From 5a27b035b04e31a007b405696253b9d4775cc6fa Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Mon, 8 Jul 2019 13:30:28 +0200 Subject: [PATCH] restructuring of the AST package --- .idea/dictionaries/irmen.xml | 3 + .idea/inspectionProfiles/Project_Default.xml | 10 + compiler/src/prog8/CompilerMain.kt | 6 + compiler/src/prog8/ast/AST.kt | 2633 ----------------- compiler/src/prog8/ast/AstRecursionChecker.kt | 120 - compiler/src/prog8/ast/AstToplevel.kt | 92 + compiler/src/prog8/ast/Interfaces.kt | 194 ++ compiler/src/prog8/ast/antlr/Antr2Kotlin.kt | 572 ++++ compiler/src/prog8/ast/base/Base.kt | 130 + compiler/src/prog8/ast/base/ErrorReporting.kt | 37 + compiler/src/prog8/ast/base/Errors.kt | 22 + compiler/src/prog8/ast/base/Extensions.kt | 84 + .../prog8/ast/expressions/AstExpressions.kt | 793 +++++ .../prog8/ast/{ => processing}/AstChecker.kt | 127 +- .../{ => processing}/AstIdentifiersChecker.kt | 58 +- .../ast/processing/AstRecursionChecker.kt | 117 + .../src/prog8/ast/processing/IAstProcessor.kt | 197 ++ .../{ => processing}/ImportedAstChecker.kt | 19 +- .../StatementReorderer.kt} | 145 +- .../VarInitValueAndAddressOfCreator.kt | 125 + .../src/prog8/ast/statements/AstStatements.kt | 639 ++++ compiler/src/prog8/astvm/AstVm.kt | 5 + compiler/src/prog8/astvm/BuiltinFunctions.kt | 2 +- compiler/src/prog8/astvm/Expressions.kt | 8 + compiler/src/prog8/astvm/VariablesCreator.kt | 4 + compiler/src/prog8/compiler/Compiler.kt | 28 +- compiler/src/prog8/compiler/RuntimeValue.kt | 5 +- compiler/src/prog8/compiler/Zeropage.kt | 3 +- .../intermediate/IntermediateProgram.kt | 8 +- .../src/prog8/compiler/target/c64/AsmGen.kt | 13 +- .../src/prog8/functions/BuiltinFunctions.kt | 37 +- compiler/src/prog8/optimizing/CallGraph.kt | 9 +- .../prog8/optimizing/ConstExprEvaluator.kt | 8 +- .../src/prog8/optimizing/ConstantFolding.kt | 106 +- compiler/src/prog8/optimizing/Extensions.kt | 2 + .../prog8/optimizing/SimplifyExpressions.kt | 29 +- .../prog8/optimizing/StatementOptimizer.kt | 8 +- compiler/src/prog8/parser/ModuleParsing.kt | 10 +- compiler/src/prog8/stackvm/Program.kt | 11 +- compiler/src/prog8/stackvm/StackVm.kt | 40 +- compiler/test/LiteralValueTests.kt | 152 +- compiler/test/RuntimeValueTests.kt | 2 +- compiler/test/StackVMOpcodeTests.kt | 8 +- compiler/test/UnitTests.kt | 12 +- 44 files changed, 3389 insertions(+), 3244 deletions(-) create mode 100644 .idea/dictionaries/irmen.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 compiler/src/prog8/ast/AST.kt delete mode 100644 compiler/src/prog8/ast/AstRecursionChecker.kt create mode 100644 compiler/src/prog8/ast/AstToplevel.kt create mode 100644 compiler/src/prog8/ast/Interfaces.kt create mode 100644 compiler/src/prog8/ast/antlr/Antr2Kotlin.kt create mode 100644 compiler/src/prog8/ast/base/Base.kt create mode 100644 compiler/src/prog8/ast/base/ErrorReporting.kt create mode 100644 compiler/src/prog8/ast/base/Errors.kt create mode 100644 compiler/src/prog8/ast/base/Extensions.kt create mode 100644 compiler/src/prog8/ast/expressions/AstExpressions.kt rename compiler/src/prog8/ast/{ => processing}/AstChecker.kt (93%) rename compiler/src/prog8/ast/{ => processing}/AstIdentifiersChecker.kt (82%) create mode 100644 compiler/src/prog8/ast/processing/AstRecursionChecker.kt create mode 100644 compiler/src/prog8/ast/processing/IAstProcessor.kt rename compiler/src/prog8/ast/{ => processing}/ImportedAstChecker.kt (78%) rename compiler/src/prog8/ast/{StmtReorderer.kt => processing/StatementReorderer.kt} (69%) create mode 100644 compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt create mode 100644 compiler/src/prog8/ast/statements/AstStatements.kt diff --git a/.idea/dictionaries/irmen.xml b/.idea/dictionaries/irmen.xml new file mode 100644 index 000000000..097618853 --- /dev/null +++ b/.idea/dictionaries/irmen.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 000000000..9c754da3c --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index e9bd3a32c..f25963683 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -1,6 +1,12 @@ package prog8 import prog8.ast.* +import prog8.ast.base.* +import prog8.ast.base.checkIdentifiers +import prog8.ast.base.checkRecursion +import prog8.ast.base.checkValid +import prog8.ast.base.reorderStatements +import prog8.ast.statements.Directive import prog8.astvm.AstVm import prog8.compiler.* import prog8.compiler.target.c64.AsmGen diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt deleted file mode 100644 index ff4d7be27..000000000 --- a/compiler/src/prog8/ast/AST.kt +++ /dev/null @@ -1,2633 +0,0 @@ -package prog8.ast - -import org.antlr.v4.runtime.IntStream -import org.antlr.v4.runtime.ParserRuleContext -import org.antlr.v4.runtime.tree.TerminalNode -import prog8.compiler.RuntimeValue -import prog8.compiler.HeapValues -import prog8.compiler.IntegerOrAddressOf -import prog8.compiler.target.c64.Petscii -import prog8.functions.BuiltinFunctions -import prog8.functions.NotConstArgumentException -import prog8.functions.builtinFunctionReturnType -import prog8.parser.CustomLexer -import prog8.parser.prog8Parser -import java.io.CharConversionException -import java.io.File -import java.nio.file.Path -import kotlin.math.abs -import kotlin.math.floor - - -/**************************** AST Data classes ****************************/ - -enum class DataType { - UBYTE, - BYTE, - UWORD, - WORD, - FLOAT, - STR, - STR_S, - ARRAY_UB, - ARRAY_B, - ARRAY_UW, - ARRAY_W, - ARRAY_F; - - /** - * is the type assignable to the given other type? - */ - infix fun isAssignableTo(targetType: DataType) = - // what types are assignable to others without loss of precision? - when(this) { - UBYTE -> targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT - BYTE -> targetType == BYTE || targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT - UWORD -> targetType == UWORD || targetType == FLOAT - WORD -> targetType == WORD || targetType==UWORD || targetType == FLOAT - FLOAT -> targetType == FLOAT - STR -> targetType == STR || targetType==STR_S - STR_S -> targetType == STR || targetType==STR_S - in ArrayDatatypes -> targetType === this - else -> false - } - - - infix fun isAssignableTo(targetTypes: Set) = targetTypes.any { this isAssignableTo it } - - infix fun biggerThan(other: DataType) = - when(this) { - in ByteDatatypes -> false - in WordDatatypes -> other in ByteDatatypes - else -> true - } -} - -enum class Register { - A, - X, - Y -} - -enum class RegisterOrPair { - A, - X, - Y, - AX, - AY, - XY -} // only used in parameter and return value specs in asm subroutines - -enum class Statusflag { - Pc, - Pz, - Pv, - Pn -} - -enum class BranchCondition { - CS, - CC, - EQ, - Z, - NE, - NZ, - VS, - VC, - MI, - NEG, - PL, - POS -} - -val IterableDatatypes = setOf( - DataType.STR, DataType.STR_S, - DataType.ARRAY_UB, DataType.ARRAY_B, - DataType.ARRAY_UW, DataType.ARRAY_W, - DataType.ARRAY_F) - -val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE) -val WordDatatypes = setOf(DataType.UWORD, DataType.WORD) -val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD) -val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT) -val StringDatatypes = setOf(DataType.STR, DataType.STR_S) -val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F) -val ArrayElementTypes = mapOf( - DataType.ARRAY_B to DataType.BYTE, - DataType.ARRAY_UB to DataType.UBYTE, - DataType.ARRAY_W to DataType.WORD, - DataType.ARRAY_UW to DataType.UWORD, - DataType.ARRAY_F to DataType.FLOAT) - - -class FatalAstException (override var message: String) : Exception(message) - -open class AstException (override var message: String) : Exception(message) - -class SyntaxError(override var message: String, val position: Position) : AstException(message) { - override fun toString() = "$position Syntax error: $message" -} - -class NameError(override var message: String, val position: Position) : AstException(message) { - override fun toString() = "$position Name error: $message" -} - -open class ExpressionError(message: String, val position: Position) : AstException(message) { - override fun toString() = "$position Error: $message" -} - -class UndefinedSymbolError(symbol: IdentifierReference) - : ExpressionError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position) - - -data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) { - override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]" -} - - -interface IAstProcessor { - fun process(program: Program) { - program.modules.forEach { process(it) } - } - - fun process(module: Module) { - module.statements = module.statements.asSequence().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.asSequence().map { it.process(this) }.toMutableList() - return block - } - - fun process(decl: VarDecl): IStatement { - decl.value = decl.value?.process(this) - decl.arraysize?.process(this) - return decl - } - - fun process(subroutine: Subroutine): IStatement { - subroutine.statements = subroutine.statements.asSequence().map { it.process(this) }.toMutableList() - return subroutine - } - - fun process(functionCall: FunctionCall): IExpression { - val newtarget = functionCall.target.process(this) - if(newtarget is IdentifierReference) - functionCall.target = newtarget - functionCall.arglist = functionCall.arglist.map { it.process(this) }.toMutableList() - return functionCall - } - - fun process(functionCallStatement: FunctionCallStatement): IStatement { - val newtarget = functionCallStatement.target.process(this) - if(newtarget is IdentifierReference) - functionCallStatement.target = newtarget - functionCallStatement.arglist = functionCallStatement.arglist.map { it.process(this) }.toMutableList() - return functionCallStatement - } - - 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 { - if(jump.identifier!=null) { - val ident = jump.identifier.process(this) - if(ident is IdentifierReference && ident!==jump.identifier) { - return Jump(null, ident, null, jump.position) - } - } - return jump - } - - fun process(ifStatement: IfStatement): IStatement { - ifStatement.condition = ifStatement.condition.process(this) - ifStatement.truepart = ifStatement.truepart.process(this) as AnonymousScope - ifStatement.elsepart = ifStatement.elsepart.process(this) as AnonymousScope - return ifStatement - } - - fun process(branchStatement: BranchStatement): IStatement { - branchStatement.truepart = branchStatement.truepart.process(this) as AnonymousScope - branchStatement.elsepart = branchStatement.elsepart.process(this) as AnonymousScope - return branchStatement - } - - fun process(range: RangeExpr): IExpression { - range.from = range.from.process(this) - range.to = range.to.process(this) - range.step = range.step.process(this) - return range - } - - fun process(label: Label): IStatement { - return label - } - - fun process(literalValue: LiteralValue): LiteralValue { - if(literalValue.arrayvalue!=null) { - for(av in literalValue.arrayvalue.withIndex()) { - val newvalue = av.value.process(this) - literalValue.arrayvalue[av.index] = newvalue - } - } - return literalValue - } - - fun process(assignment: Assignment): IStatement { - assignment.targets = assignment.targets.map { it.process(this) } - assignment.value = assignment.value.process(this) - return assignment - } - - fun process(postIncrDecr: PostIncrDecr): IStatement { - postIncrDecr.target = postIncrDecr.target.process(this) - return postIncrDecr - } - - fun process(contStmt: Continue): IStatement { - return contStmt - } - - fun process(breakStmt: Break): IStatement { - return breakStmt - } - - fun process(forLoop: ForLoop): IStatement { - forLoop.loopVar?.process(this) - forLoop.iterable = forLoop.iterable.process(this) - forLoop.body = forLoop.body.process(this) as AnonymousScope - return forLoop - } - - fun process(whileLoop: WhileLoop): IStatement { - whileLoop.condition = whileLoop.condition.process(this) - whileLoop.body = whileLoop.body.process(this) as AnonymousScope - return whileLoop - } - - fun process(repeatLoop: RepeatLoop): IStatement { - repeatLoop.untilCondition = repeatLoop.untilCondition.process(this) - repeatLoop.body = repeatLoop.body.process(this) as AnonymousScope - return repeatLoop - } - - fun process(returnStmt: Return): IStatement { - returnStmt.values = returnStmt.values.map { it.process(this) } - return returnStmt - } - - fun process(arrayIndexedExpression: ArrayIndexedExpression): IExpression { - arrayIndexedExpression.identifier.process(this) - arrayIndexedExpression.arrayspec.process(this) - return arrayIndexedExpression - } - - fun process(assignTarget: AssignTarget): AssignTarget { - assignTarget.arrayindexed?.process(this) - assignTarget.identifier?.process(this) - assignTarget.memoryAddress?.process(this) - return assignTarget - } - - fun process(scope: AnonymousScope): IStatement { - scope.statements = scope.statements.asSequence().map { it.process(this) }.toMutableList() - return scope - } - - fun process(typecast: TypecastExpression): IExpression { - typecast.expression = typecast.expression.process(this) - return typecast - } - - fun process(memread: DirectMemoryRead): IExpression { - memread.addressExpression = memread.addressExpression.process(this) - return memread - } - - fun process(memwrite: DirectMemoryWrite): IExpression { - memwrite.addressExpression = memwrite.addressExpression.process(this) - return memwrite - } - - fun process(addressOf: AddressOf): IExpression { - addressOf.identifier.process(this) - return addressOf - } - - fun process(inlineAssembly: InlineAssembly): IStatement { - return inlineAssembly - } -} - - -interface Node { - val position: Position - var parent: Node // will be linked correctly later (late init) - fun linkParents(parent: Node) - - fun definingModule(): Module { - if(this is Module) - return this - return findParentNode(this)!! - } - - fun definingSubroutine(): Subroutine? = findParentNode(this) - - fun definingScope(): INameScope { - val scope = findParentNode(this) - if(scope!=null) { - return scope - } - if(this is Label && this.name.startsWith("builtin::")) { - return BuiltinFunctionScopePlaceholder - } - if(this is GlobalNamespace) - return this - throw FatalAstException("scope missing from $this") - } -} - - -// find the parent node of a specific type or interface -// (useful to figure out in what namespace/block something is defined, etc) -inline fun findParentNode(node: Node): T? { - var candidate = node.parent - while(candidate !is T && candidate !is ParentSentinel) - candidate = candidate.parent - return if(candidate is ParentSentinel) - null - else - candidate as T -} - - -interface IStatement : Node { - fun process(processor: IAstProcessor) : IStatement - fun makeScopedName(name: String): String { - // easy way out is to always return the full scoped name. - // it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now. - // and like this, we can cache the name even, - // like in a lazy property on the statement object itself (label, subroutine, vardecl) - val scope = mutableListOf() - var statementScope = this.parent - while(statementScope !is ParentSentinel && statementScope !is Module) { - if(statementScope is INameScope) { - scope.add(0, statementScope.name) - } - statementScope = statementScope.parent - } - if(name.isNotEmpty()) - scope.add(name) - return scope.joinToString(".") - } - - val expensiveToInline: Boolean - - fun definingBlock(): Block { - if(this is Block) - return this - return findParentNode(this)!! - } -} - - -interface IFunctionCall { - var target: IdentifierReference - var arglist: MutableList -} - - -interface INameScope { - val name: String - val position: Position - val statements: MutableList - val parent: Node - - fun linkParents(parent: Node) - - 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.containsCodeOrVars()) - subscopes[stmt.elsepart.name] = stmt.elsepart - } - is IfStatement -> { - subscopes[stmt.truepart.name] = stmt.truepart - if(stmt.elsepart.containsCodeOrVars()) - subscopes[stmt.elsepart.name] = stmt.elsepart - } - } - } - return subscopes - } - - fun getLabelOrVariable(name: String): IStatement? { - // TODO this is called A LOT and could perhaps be optimized a bit more, but adding a 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 allDefinedSymbols(): List> { - 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, localContext: Node) : IStatement? { - if(scopedName.size>1) { - // 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?.subScopes()?.get(name) - if(scope==null) - break - } - if(scope!=null) { - val result = scope.getLabelOrVariable(scopedName.last()) - if(result!=null) - return result - return scope.subScopes()[scopedName.last()] as IStatement? - } - } - 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.subScopes()[scopedName[0]] as IStatement? - if (subscope != null) - return subscope - // not found in this scope, look one higher up - statementScope = statementScope.parent - } - return null - } - } - - fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"} - fun containsNoCodeNorVars() = !containsCodeOrVars() - - fun remove(stmt: IStatement) { - if(!statements.remove(stmt)) - throw FatalAstException("stmt to remove wasn't found in scope") - } -} - -object ParentSentinel : Node { - override val position = Position("<>", 0, 0, 0) - override var parent: Node = this - override fun linkParents(parent: Node) {} -} - -object BuiltinFunctionScopePlaceholder : INameScope { - override val name = "<>" - override val position = Position("<>", 0, 0, 0) - override var statements = mutableListOf() - override var parent: Node = ParentSentinel - override fun linkParents(parent: Node) {} -} - -class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : IStatement { - override var parent: Node = ParentSentinel - override fun linkParents(parent: Node) {} - override fun process(processor: IAstProcessor): IStatement = this - override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder - override val expensiveToInline = false -} - - - -/*********** Everything starts from here, the Program; zero or more modules *************/ - -class Program(val name: String, val modules: MutableList) { - val namespace = GlobalNamespace(modules) - val heap = HeapValues() - - val loadAddress: Int - get() = modules.first().loadAddress - - fun entrypoint(): Subroutine? { - val mainBlocks = modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block } - if(mainBlocks.size > 1) - throw FatalAstException("more than one 'main' block") - return if(mainBlocks.isEmpty()) { - null - } else { - mainBlocks[0].subScopes()["start"] as Subroutine? - } - } -} - - -class Module(override val name: String, - override var statements: MutableList, - override val position: Position, - val isLibraryModule: Boolean, - val source: Path) : Node, INameScope { - override lateinit var parent: Node - lateinit var program: Program - val importedBy = mutableListOf() - val imports = mutableSetOf() - - var loadAddress: Int = 0 // can be set with the %address directive - - override fun linkParents(parent: Node) { - this.parent = parent - statements.forEach {it.linkParents(this)} - } - - override fun definingScope(): INameScope = program.namespace - - override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)" -} - - -class GlobalNamespace(val modules: List): Node, INameScope { - override val name = "<<>>" - override val position = Position("<<>>", 0, 0, 0) - override val statements = mutableListOf() - override var parent: Node = ParentSentinel - - override fun linkParents(parent: Node) { - modules.forEach { it.linkParents(this) } - } - - override fun lookup(scopedName: List, localContext: Node): IStatement? { - if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) { - // builtin functions always exist, return a dummy localContext for them - val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position) - builtinPlaceholder.parent = ParentSentinel - return builtinPlaceholder - } - - val stmt = localContext.definingModule().lookup(scopedName, localContext) - return when (stmt) { - is Label, is VarDecl, is Block, is Subroutine -> stmt - null -> null - else -> throw NameError("wrong identifier target: $stmt", stmt.position) - } - } -} - - -class Block(override val name: String, - val address: Int?, - override var statements: MutableList, - val isInLibrary: Boolean, - override val position: Position) : IStatement, INameScope { - override lateinit var parent: Node - override val expensiveToInline - get() = statements.any { it.expensiveToInline } - - override fun linkParents(parent: Node) { - this.parent = parent - statements.forEach {it.linkParents(this)} - } - - override fun process(processor: IAstProcessor) = processor.process(this) - - override fun toString(): String { - return "Block(name=$name, address=$address, ${statements.size} statements)" - } - - fun options() = statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.map {it.name!!}.toSet() -} - - -data class Directive(val directive: String, val args: List, override val position: Position) : IStatement { - override lateinit var parent: Node - override val expensiveToInline = false - - override fun linkParents(parent: Node) { - this.parent = parent - args.forEach{it.linkParents(this)} - } - - override fun process(processor: IAstProcessor) = processor.process(this) -} - - -data class DirectiveArg(val str: String?, val name: String?, val int: Int?, override val position: Position) : Node { - override lateinit var parent: Node - - override fun linkParents(parent: Node) { - this.parent = parent - } -} - - -data class Label(val name: String, override val position: Position) : IStatement { - override lateinit var parent: Node - override val expensiveToInline = false - - override fun linkParents(parent: Node) { - this.parent = parent - } - - override fun process(processor: IAstProcessor) = processor.process(this) - - override fun toString(): String { - return "Label(name=$name, pos=$position)" - } - - val scopedname: String by lazy { makeScopedName(name) } -} - - -open class Return(var values: List, override val position: Position) : IStatement { - override lateinit var parent: Node - override val expensiveToInline = values.any { it !is LiteralValue } - - override fun linkParents(parent: Node) { - this.parent = parent - values.forEach {it.linkParents(this)} - } - - override fun process(processor: IAstProcessor) = processor.process(this) - - override fun toString(): String { - return "Return(values: $values, pos=$position)" - } -} - - -class ReturnFromIrq(override val position: Position) : Return(emptyList(), position) { - override fun process(processor: IAstProcessor) = this - - override fun toString(): String { - return "ReturnFromIrq(pos=$position)" - } -} - - -class Continue(override val position: Position) : IStatement { - override lateinit var parent: Node - override val expensiveToInline = false - - override fun linkParents(parent: Node) { - this.parent=parent - } - - override fun process(processor: IAstProcessor) = processor.process(this) -} - -class Break(override val position: Position) : IStatement { - override lateinit var parent: Node - override val expensiveToInline = false - - override fun linkParents(parent: Node) { - this.parent=parent - } - - override fun process(processor: IAstProcessor) = processor.process(this) -} - - -class ArrayIndex(var index: IExpression, override val position: Position) : Node { - override lateinit var parent: Node - - override fun linkParents(parent: Node) { - this.parent = parent - index.linkParents(this) - } - - companion object { - fun forArray(v: LiteralValue, heap: HeapValues): ArrayIndex { - val arraySize = v.arrayvalue?.size ?: heap.get(v.heapId!!).arraysize - return ArrayIndex(LiteralValue.optimalNumeric(arraySize, v.position), v.position) - } - } - - fun process(processor: IAstProcessor) { - index = index.process(processor) - } - - override fun toString(): String { - return("ArrayIndex($index, pos=$position)") - } - - fun size() = (index as? LiteralValue)?.asIntegerValue -} - - -enum class VarDeclType { - VAR, - CONST, - MEMORY -} - -class VarDecl(val type: VarDeclType, - private val declaredDatatype: DataType, - val zeropage: Boolean, - var arraysize: ArrayIndex?, - val name: String, - var value: IExpression?, - val isArray: Boolean, - val autoGenerated: Boolean, - override val position: Position) : IStatement { - override lateinit var parent: Node - override val expensiveToInline - get() = value!=null && value !is LiteralValue - - val datatypeErrors = mutableListOf() // don't crash at init time, report them in the AstChecker - val datatype = - if (!isArray) declaredDatatype - else when (declaredDatatype) { - DataType.UBYTE -> DataType.ARRAY_UB - DataType.BYTE -> DataType.ARRAY_B - DataType.UWORD -> DataType.ARRAY_UW - DataType.WORD -> DataType.ARRAY_W - DataType.FLOAT -> DataType.ARRAY_F - else -> { - datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats", position)) - DataType.UBYTE - } - } - - override fun linkParents(parent: Node) { - this.parent = parent - arraysize?.linkParents(this) - value?.linkParents(this) - } - - override fun process(processor: IAstProcessor) = processor.process(this) - - val scopedname: String by lazy { makeScopedName(name) } - - override fun toString(): String { - return "VarDecl(name=$name, vartype=$type, datatype=$datatype, array=$isArray, value=$value, pos=$position)" - } - - fun asDefaultValueDecl(parent: Node?): VarDecl { - val constValue = when(declaredDatatype) { - DataType.UBYTE -> LiteralValue(DataType.UBYTE, 0, position=position) - DataType.BYTE -> LiteralValue(DataType.BYTE, 0, position=position) - DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue=0, position=position) - DataType.WORD -> LiteralValue(DataType.WORD, wordvalue=0, position=position) - DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue=0.0, position=position) - else -> throw FatalAstException("can only set a default value for a numeric type") - } - val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, constValue, isArray, true, position) - if(parent!=null) - decl.linkParents(parent) - return decl - } -} - - -open class Assignment(var targets: List, val aug_op : String?, var value: IExpression, override val position: Position) : IStatement { - override lateinit var parent: Node - override val expensiveToInline - get() = value !is LiteralValue - - override fun linkParents(parent: Node) { - this.parent = parent - targets.forEach { it.linkParents(this) } - value.linkParents(this) - } - - override fun process(processor: IAstProcessor) = processor.process(this) - - override fun toString(): String { - return("Assignment(augop: $aug_op, targets: $targets, value: $value, pos=$position)") - } - - val singleTarget: AssignTarget? - get() { - return targets.singleOrNull() // common case - } -} - -// This is a special class so the compiler can see if the assignments are for initializing the vars in the scope, -// or just a regular assignment. It may optimize the initialization step from this. -class VariableInitializationAssignment(target: AssignTarget, aug_op: String?, value: IExpression, position: Position) - : Assignment(listOf(target), aug_op, value, position) - - -data class AssignTarget(val register: Register?, - val identifier: IdentifierReference?, - val arrayindexed: ArrayIndexedExpression?, - var memoryAddress: DirectMemoryWrite?, - override val position: Position) : Node { - override lateinit var parent: Node - - override fun linkParents(parent: Node) { - this.parent = parent - identifier?.linkParents(this) - arrayindexed?.linkParents(this) - memoryAddress?.linkParents(this) - } - - fun process(processor: IAstProcessor) = processor.process(this) - - companion object { - fun fromExpr(expr: IExpression): AssignTarget { - return when (expr) { - is RegisterExpr -> AssignTarget(expr.register, null, null, null, expr.position) - is IdentifierReference -> AssignTarget(null, expr, null, null, expr.position) - is ArrayIndexedExpression -> AssignTarget(null, null, expr, null, expr.position) - is DirectMemoryRead -> AssignTarget(null, null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position) - is DirectMemoryWrite -> AssignTarget(null, null, null, expr, expr.position) - else -> throw FatalAstException("invalid expression object $expr") - } - } - } - - fun inferType(program: Program, stmt: IStatement): DataType? { - if(register!=null) - return DataType.UBYTE - - if(identifier!=null) { - val symbol = program.namespace.lookup(identifier.nameInSource, stmt) ?: return null - if (symbol is VarDecl) return symbol.datatype - } - - if(arrayindexed!=null) { - val dt = arrayindexed.inferType(program) - if(dt!=null) - return dt - } - - if(memoryAddress!=null) - return DataType.UBYTE - - return null - } - - fun shortString(withTypePrefix: Boolean=false): String { - if(register!=null) - return (if(withTypePrefix) "0register::" else "") + register.name - if(identifier!=null) - return (if(withTypePrefix) "3identifier::" else "") + identifier.nameInSource.last() - if(arrayindexed!=null) - return (if(withTypePrefix) "2arrayidx::" else "") + arrayindexed.identifier.nameInSource.last() - val address = memoryAddress?.addressExpression - if(address is LiteralValue) - return (if(withTypePrefix) "1address::" else "") +address.asIntegerValue.toString() - return if(withTypePrefix) "???::???" else "???" - } - - fun isMemoryMapped(namespace: INameScope): Boolean = - memoryAddress!=null || (identifier?.targetVarDecl(namespace)?.type==VarDeclType.MEMORY) - - infix fun isSameAs(value: IExpression): Boolean { - return when { - this.memoryAddress!=null -> false - this.register!=null -> value is RegisterExpr && value.register==register - this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier.nameInSource - this.arrayindexed!=null -> value is ArrayIndexedExpression && - value.identifier.nameInSource==arrayindexed.identifier.nameInSource && - value.arrayspec.size()!=null && - arrayindexed.arrayspec.size()!=null && - value.arrayspec.size()==arrayindexed.arrayspec.size() - else -> false - } - } - - fun isSameAs(other: AssignTarget, program: Program): Boolean { - if(this===other) - return true - if(this.register!=null && other.register!=null) - return this.register==other.register - if(this.identifier!=null && other.identifier!=null) - return this.identifier.nameInSource==other.identifier.nameInSource - if(this.memoryAddress!=null && other.memoryAddress!=null) { - val addr1 = this.memoryAddress!!.addressExpression.constValue(program) - val addr2 = other.memoryAddress!!.addressExpression.constValue(program) - return addr1!=null && addr2!=null && addr1==addr2 - } - if(this.arrayindexed!=null && other.arrayindexed!=null) { - if(this.arrayindexed.identifier.nameInSource == other.arrayindexed.identifier.nameInSource) { - val x1 = this.arrayindexed.arrayspec.index.constValue(program) - val x2 = other.arrayindexed.arrayspec.index.constValue(program) - return x1!=null && x2!=null && x1==x2 - } - } - return false - } - - fun isNotMemory(namespace: INameScope): Boolean { - if(this.register!=null) - return true - if(this.memoryAddress!=null) - return false - if(this.arrayindexed!=null) { - val targetStmt = this.arrayindexed.identifier.targetVarDecl(namespace) - if(targetStmt!=null) - return targetStmt.type!=VarDeclType.MEMORY - } - if(this.identifier!=null) { - val targetStmt = this.identifier.targetVarDecl(namespace) - if(targetStmt!=null) - return targetStmt.type!=VarDeclType.MEMORY - } - return false - } -} - - -interface IExpression: Node { - fun constValue(program: Program): LiteralValue? - fun process(processor: IAstProcessor): IExpression - fun referencesIdentifier(name: String): Boolean - fun inferType(program: Program): DataType? - - infix fun isSameAs(other: IExpression): Boolean { - if(this===other) - return true - when(this) { - is RegisterExpr -> - return (other is RegisterExpr && other.register==register) - is IdentifierReference -> - return (other is IdentifierReference && other.nameInSource==nameInSource) - is PrefixExpression -> - return (other is PrefixExpression && other.operator==operator && other.expression isSameAs expression) - is BinaryExpression -> - return (other is BinaryExpression && other.operator==operator - && other.left isSameAs left - && other.right isSameAs right) - is ArrayIndexedExpression -> { - return (other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource - && other.arrayspec.index isSameAs arrayspec.index) - } - is LiteralValue -> return (other is LiteralValue && other==this) - } - return false - } -} - - -// note: some expression elements are mutable, to be able to rewrite/process the expression tree - -class PrefixExpression(val operator: String, var expression: IExpression, override val position: Position) : IExpression { - override lateinit var parent: Node - - override fun linkParents(parent: Node) { - this.parent = parent - expression.linkParents(this) - } - - override fun constValue(program: Program): LiteralValue? = null - override fun process(processor: IAstProcessor) = processor.process(this) - override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name) - override fun inferType(program: Program): DataType? = expression.inferType(program) - - override fun toString(): String { - return "Prefix($operator $expression)" - } -} - - -class BinaryExpression(var left: IExpression, var operator: String, var right: IExpression, override val position: Position) : IExpression { - override lateinit var parent: Node - - override fun linkParents(parent: Node) { - this.parent = parent - left.linkParents(this) - right.linkParents(this) - } - - override fun toString(): String { - return "[$left $operator $right]" - } - - // binary expression should actually have been optimized away into a single value, before const value was requested... - override fun constValue(program: Program): LiteralValue? = null - - override fun process(processor: IAstProcessor) = processor.process(this) - override fun referencesIdentifier(name: String) = left.referencesIdentifier(name) || right.referencesIdentifier(name) - override fun inferType(program: Program): DataType? { - val leftDt = left.inferType(program) - val rightDt = right.inferType(program) - return when (operator) { - "+", "-", "*", "**", "%" -> if (leftDt == null || rightDt == null) null else { - try { - arithmeticOpDt(leftDt, rightDt) - } catch (x: FatalAstException) { - null - } - } - "/" -> if (leftDt == null || rightDt == null) null else divisionOpDt(leftDt, rightDt) - "&" -> leftDt - "|" -> leftDt - "^" -> leftDt - "and", "or", "xor", - "<", ">", - "<=", ">=", - "==", "!=" -> DataType.UBYTE - "<<", ">>" -> leftDt - else -> throw FatalAstException("resulting datatype check for invalid operator $operator") - } - } - - companion object { - fun divisionOpDt(leftDt: DataType, rightDt: DataType): DataType { - return when (leftDt) { - DataType.UBYTE -> when (rightDt) { - DataType.UBYTE, DataType.UWORD -> DataType.UBYTE - DataType.BYTE, DataType.WORD -> DataType.WORD - DataType.FLOAT -> DataType.BYTE - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - DataType.BYTE -> when (rightDt) { - in NumericDatatypes -> DataType.BYTE - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - DataType.UWORD -> when (rightDt) { - DataType.UBYTE, DataType.UWORD -> DataType.UWORD - DataType.BYTE, DataType.WORD -> DataType.WORD - DataType.FLOAT -> DataType.FLOAT - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - DataType.WORD -> when (rightDt) { - in NumericDatatypes -> DataType.WORD - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - DataType.FLOAT -> when (rightDt) { - in NumericDatatypes -> DataType.FLOAT - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - } - - fun arithmeticOpDt(leftDt: DataType, rightDt: DataType): DataType { - return when (leftDt) { - DataType.UBYTE -> when (rightDt) { - DataType.UBYTE -> DataType.UBYTE - DataType.BYTE -> DataType.BYTE - DataType.UWORD -> DataType.UWORD - DataType.WORD -> DataType.WORD - DataType.FLOAT -> DataType.FLOAT - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - DataType.BYTE -> when (rightDt) { - in ByteDatatypes -> DataType.BYTE - in WordDatatypes -> DataType.WORD - DataType.FLOAT -> DataType.FLOAT - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - DataType.UWORD -> when (rightDt) { - DataType.UBYTE, DataType.UWORD -> DataType.UWORD - DataType.BYTE, DataType.WORD -> DataType.WORD - DataType.FLOAT -> DataType.FLOAT - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - DataType.WORD -> when (rightDt) { - in IntegerDatatypes -> DataType.WORD - DataType.FLOAT -> DataType.FLOAT - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - DataType.FLOAT -> when (rightDt) { - in NumericDatatypes -> DataType.FLOAT - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - } - } - - fun commonDatatype(leftDt: DataType, rightDt: DataType, - left: IExpression, right: IExpression): Pair { - // byte + byte -> byte - // byte + word -> word - // word + byte -> word - // word + word -> word - // a combination with a float will be float (but give a warning about this!) - - if(this.operator=="/") { - // division is a bit weird, don't cast the operands - val commondt = divisionOpDt(leftDt, rightDt) - return Pair(commondt, null) - } - - return when (leftDt) { - DataType.UBYTE -> { - when (rightDt) { - DataType.UBYTE -> Pair(DataType.UBYTE, null) - DataType.BYTE -> Pair(DataType.BYTE, left) - DataType.UWORD -> Pair(DataType.UWORD, left) - DataType.WORD -> Pair(DataType.WORD, left) - DataType.FLOAT -> Pair(DataType.FLOAT, left) - else -> throw FatalAstException("non-numeric datatype $rightDt") - } - } - DataType.BYTE -> { - when (rightDt) { - DataType.UBYTE -> Pair(DataType.BYTE, right) - DataType.BYTE -> Pair(DataType.BYTE, null) - DataType.UWORD -> Pair(DataType.WORD, left) - DataType.WORD -> Pair(DataType.WORD, left) - DataType.FLOAT -> Pair(DataType.FLOAT, left) - else -> throw FatalAstException("non-numeric datatype $rightDt") - } - } - DataType.UWORD -> { - when (rightDt) { - DataType.UBYTE -> Pair(DataType.UWORD, right) - DataType.BYTE -> Pair(DataType.UWORD, right) - DataType.UWORD -> Pair(DataType.UWORD, null) - DataType.WORD -> Pair(DataType.WORD, left) - DataType.FLOAT -> Pair(DataType.FLOAT, left) - else -> throw FatalAstException("non-numeric datatype $rightDt") - } - } - DataType.WORD -> { - when (rightDt) { - DataType.UBYTE -> Pair(DataType.WORD, right) - DataType.BYTE -> Pair(DataType.WORD, right) - DataType.UWORD -> Pair(DataType.WORD, right) - DataType.WORD -> Pair(DataType.WORD, null) - DataType.FLOAT -> Pair(DataType.FLOAT, left) - else -> throw FatalAstException("non-numeric datatype $rightDt") - } - } - DataType.FLOAT -> { - Pair(DataType.FLOAT, right) - } - else -> throw FatalAstException("non-numeric datatype $leftDt") - } - } -} - -class ArrayIndexedExpression(val identifier: IdentifierReference, - var arrayspec: ArrayIndex, - override val position: Position) : IExpression { - override lateinit var parent: Node - override fun linkParents(parent: Node) { - this.parent = parent - identifier.linkParents(this) - arrayspec.linkParents(this) - } - - override fun constValue(program: Program): LiteralValue? = null - override fun process(processor: IAstProcessor): IExpression = processor.process(this) - override fun referencesIdentifier(name: String) = identifier.referencesIdentifier(name) - - override fun inferType(program: Program): DataType? { - val target = identifier.targetStatement(program.namespace) - if (target is VarDecl) { - return when (target.datatype) { - in NumericDatatypes -> null - in StringDatatypes -> DataType.UBYTE - DataType.ARRAY_UB -> DataType.UBYTE - DataType.ARRAY_B -> DataType.BYTE - DataType.ARRAY_UW -> DataType.UWORD - DataType.ARRAY_W -> DataType.WORD - DataType.ARRAY_F -> DataType.FLOAT - else -> throw FatalAstException("invalid dt") - } - } - return null - } - - override fun toString(): String { - return "ArrayIndexed(ident=$identifier, arraysize=$arrayspec; pos=$position)" - } -} - - -class TypecastExpression(var expression: IExpression, var type: DataType, val implicit: Boolean, override val position: Position) : IExpression { - override lateinit var parent: Node - - override fun linkParents(parent: Node) { - this.parent = parent - expression.linkParents(this) - } - - override fun process(processor: IAstProcessor) = processor.process(this) - override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name) - override fun inferType(program: Program): DataType? = type - override fun constValue(program: Program): LiteralValue? { - val cv = expression.constValue(program) ?: return null - val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type) - return LiteralValue.fromNumber(value.numericValue(), value.type, position) - } - - override fun toString(): String { - return "Typecast($expression as $type)" - } -} - - -data class AddressOf(val identifier: IdentifierReference, override val position: Position) : IExpression { - override lateinit var parent: Node - - override fun linkParents(parent: Node) { - this.parent = parent - identifier.parent=this - } - - var scopedname: String? = null // will be set in a later state by the compiler - override fun constValue(program: Program): LiteralValue? = null - override fun referencesIdentifier(name: String) = false - override fun inferType(program: Program) = DataType.UWORD - override fun process(processor: IAstProcessor) = processor.process(this) -} - - -class DirectMemoryRead(var addressExpression: IExpression, override val position: Position) : IExpression { - override lateinit var parent: Node - - override fun linkParents(parent: Node) { - this.parent = parent - this.addressExpression.linkParents(this) - } - - override fun process(processor: IAstProcessor) = processor.process(this) - override fun referencesIdentifier(name: String) = false - override fun inferType(program: Program): DataType? = DataType.UBYTE - override fun constValue(program: Program): LiteralValue? = null - - override fun toString(): String { - return "DirectMemoryRead($addressExpression)" - } -} - - -class DirectMemoryWrite(var addressExpression: IExpression, override val position: Position) : IExpression { - override lateinit var parent: Node - - override fun linkParents(parent: Node) { - this.parent = parent - this.addressExpression.linkParents(this) - } - - override fun process(processor: IAstProcessor) = processor.process(this) - override fun referencesIdentifier(name: String) = false - override fun inferType(program: Program): DataType? = DataType.UBYTE - override fun constValue(program: Program): LiteralValue? = null - - override fun toString(): String { - return "DirectMemoryWrite($addressExpression)" - } -} - - -private data class NumericLiteral(val number: Number, val datatype: DataType) - - -open class LiteralValue(val type: DataType, - val bytevalue: Short? = null, - val wordvalue: Int? = null, - val floatvalue: Double? = null, - val strvalue: String? = null, - val arrayvalue: Array? = null, - initHeapId: Int? =null, - override val position: Position) : IExpression { - override lateinit var parent: Node - - override fun referencesIdentifier(name: String) = arrayvalue?.any { it.referencesIdentifier(name) } ?: false - - val isString = type in StringDatatypes - val isNumeric = type in NumericDatatypes - val isArray = type in ArrayDatatypes - var heapId = initHeapId - private set - - companion object { - fun fromBoolean(bool: Boolean, position: Position) = - LiteralValue(DataType.UBYTE, bytevalue = if(bool) 1 else 0, position=position) - - fun fromNumber(value: Number, type: DataType, position: Position) : LiteralValue { - return when(type) { - in ByteDatatypes -> LiteralValue(type, bytevalue = value.toShort(), position = position) - in WordDatatypes -> LiteralValue(type, wordvalue = value.toInt(), position = position) - DataType.FLOAT -> LiteralValue(type, floatvalue = value.toDouble(), position = position) - else -> throw FatalAstException("non numeric datatype") - } - } - - fun optimalNumeric(value: Number, position: Position): LiteralValue { - return if(value is Double) { - LiteralValue(DataType.FLOAT, floatvalue = value, position = position) - } else { - val intval = value.toInt() - when (intval) { - in 0..255 -> LiteralValue(DataType.UBYTE, bytevalue=intval.toShort(), position = position) - in -128..127 -> LiteralValue(DataType.BYTE, bytevalue=intval.toShort(), position = position) - in 0..65535 -> LiteralValue(DataType.UWORD, wordvalue = intval, position = position) - in -32768..32767 -> LiteralValue(DataType.WORD, wordvalue = intval, position = position) - else -> LiteralValue(DataType.FLOAT, floatvalue = intval.toDouble(), position = position) - } - } - } - - fun optimalInteger(value: Number, position: Position): LiteralValue { - val intval = value.toInt() - if(intval.toDouble() != value.toDouble()) - throw FatalAstException("value is not an integer: $value") - return when (intval) { - in 0..255 -> LiteralValue(DataType.UBYTE, bytevalue=value.toShort(), position = position) - in -128..127 -> LiteralValue(DataType.BYTE, bytevalue=value.toShort(), position = position) - in 0..65535 -> LiteralValue(DataType.UWORD, wordvalue = value.toInt(), position = position) - else -> throw FatalAstException("integer overflow: $value") - } - } - } - - init { - when(type){ - in ByteDatatypes -> if(bytevalue==null) throw FatalAstException("literal value missing bytevalue") - in WordDatatypes -> if(wordvalue==null) throw FatalAstException("literal value missing wordvalue") - DataType.FLOAT -> if(floatvalue==null) throw FatalAstException("literal value missing floatvalue") - in StringDatatypes -> - if(strvalue==null && heapId==null) throw FatalAstException("literal value missing strvalue/heapId") - in ArrayDatatypes -> - if(arrayvalue==null && heapId==null) throw FatalAstException("literal value missing arrayvalue/heapId") - else -> throw FatalAstException("invalid type $type") - } - if(bytevalue==null && wordvalue==null && floatvalue==null && arrayvalue==null && strvalue==null && heapId==null) - throw FatalAstException("literal value without actual value") - } - - val asNumericValue: Number? = when { - bytevalue!=null -> bytevalue - wordvalue!=null -> wordvalue - floatvalue!=null -> floatvalue - else -> null - } - - val asIntegerValue: Int? = when { - bytevalue!=null -> bytevalue.toInt() - wordvalue!=null -> wordvalue - // don't round a float value, otherwise code will not detect that it's not an integer - else -> null - } - - val asBooleanValue: Boolean = - (floatvalue!=null && floatvalue != 0.0) || - (bytevalue!=null && bytevalue != 0.toShort()) || - (wordvalue!=null && wordvalue != 0) || - (strvalue!=null && strvalue.isNotEmpty()) || - (arrayvalue != null && arrayvalue.isNotEmpty()) - - override fun linkParents(parent: Node) { - this.parent = parent - arrayvalue?.forEach {it.linkParents(this)} - } - - override fun constValue(program: Program): LiteralValue? { - if(arrayvalue!=null) { - for(v in arrayvalue) { - if(v.constValue(program)==null) return null - } - } - return this - } - - override fun process(processor: IAstProcessor) = processor.process(this) - - override fun toString(): String { - val vstr = when(type) { - DataType.UBYTE -> "ubyte:$bytevalue" - DataType.BYTE -> "byte:$bytevalue" - DataType.UWORD -> "uword:$wordvalue" - DataType.WORD -> "word:$wordvalue" - DataType.FLOAT -> "float:$floatvalue" - in StringDatatypes -> "str:$strvalue" - in ArrayDatatypes -> "array:$arrayvalue" - else -> throw FatalAstException("weird datatype") - } - return "LiteralValue($vstr)" - } - - override fun inferType(program: Program) = type - - override fun hashCode(): Int { - val bh = bytevalue?.hashCode() ?: 0x10001234 - val wh = wordvalue?.hashCode() ?: 0x01002345 - val fh = floatvalue?.hashCode() ?: 0x00103456 - val sh = strvalue?.hashCode() ?: 0x00014567 - val ah = arrayvalue?.hashCode() ?: 0x11119876 - var hash = bh * 31 xor wh - hash = hash*31 xor fh - hash = hash*31 xor sh - hash = hash*31 xor ah - hash = hash*31 xor type.hashCode() - return hash - } - - override fun equals(other: Any?): Boolean { - if(other==null || other !is LiteralValue) - return false - if(isNumeric && other.isNumeric) - return asNumericValue?.toDouble()==other.asNumericValue?.toDouble() - if(isArray && other.isArray) - return arrayvalue!!.contentEquals(other.arrayvalue!!) && heapId==other.heapId - if(isString && other.isString) - return strvalue==other.strvalue && heapId==other.heapId - - if(type!=other.type) - return false - - return compareTo(other) == 0 - } - - operator fun compareTo(other: LiteralValue): Int { - val numLeft = asNumericValue?.toDouble() - val numRight = other.asNumericValue?.toDouble() - if(numLeft!=null && numRight!=null) - return numLeft.compareTo(numRight) - - if(strvalue!=null && other.strvalue!=null) - return strvalue.compareTo(other.strvalue) - - throw ExpressionError("cannot order compare type $type with ${other.type}", other.position) - } - - fun intoDatatype(targettype: DataType): LiteralValue? { - if(type==targettype) - return this - when(type) { - DataType.UBYTE -> { - if(targettype==DataType.BYTE && bytevalue!! <= 127) - return LiteralValue(targettype, bytevalue=bytevalue, position=position) - if(targettype==DataType.WORD || targettype==DataType.UWORD) - return LiteralValue(targettype, wordvalue=bytevalue!!.toInt(), position=position) - if(targettype==DataType.FLOAT) - return LiteralValue(targettype, floatvalue=bytevalue!!.toDouble(), position=position) - } - DataType.BYTE -> { - if(targettype==DataType.UBYTE && bytevalue!! >= 0) - return LiteralValue(targettype, bytevalue=bytevalue, position=position) - if(targettype==DataType.UWORD && bytevalue!! >= 0) - return LiteralValue(targettype, wordvalue=bytevalue.toInt(), position=position) - if(targettype==DataType.WORD) - return LiteralValue(targettype, wordvalue=bytevalue!!.toInt(), position=position) - if(targettype==DataType.FLOAT) - return LiteralValue(targettype, floatvalue=bytevalue!!.toDouble(), position=position) - } - DataType.UWORD -> { - if(targettype==DataType.BYTE && wordvalue!! <= 127) - return LiteralValue(targettype, bytevalue=wordvalue.toShort(), position=position) - if(targettype==DataType.UBYTE && wordvalue!! <= 255) - return LiteralValue(targettype, bytevalue=wordvalue.toShort(), position=position) - if(targettype==DataType.WORD && wordvalue!! <= 32767) - return LiteralValue(targettype, wordvalue=wordvalue, position=position) - if(targettype==DataType.FLOAT) - return LiteralValue(targettype, floatvalue=wordvalue!!.toDouble(), position=position) - } - DataType.WORD -> { - if(targettype==DataType.BYTE && wordvalue!! in -128..127) - return LiteralValue(targettype, bytevalue=wordvalue.toShort(), position=position) - if(targettype==DataType.UBYTE && wordvalue!! in 0..255) - return LiteralValue(targettype, bytevalue=wordvalue.toShort(), position=position) - if(targettype==DataType.UWORD && wordvalue!! >=0) - return LiteralValue(targettype, wordvalue=wordvalue, position=position) - if(targettype==DataType.FLOAT) - return LiteralValue(targettype, floatvalue=wordvalue!!.toDouble(), position=position) - } - DataType.FLOAT -> { - if(floor(floatvalue!!)==floatvalue) { - val value = floatvalue.toInt() - if (targettype == DataType.BYTE && value in -128..127) - return LiteralValue(targettype, bytevalue = value.toShort(), position = position) - if (targettype == DataType.UBYTE && value in 0..255) - return LiteralValue(targettype, bytevalue = value.toShort(), position = position) - if (targettype == DataType.WORD && value in -32768..32767) - return LiteralValue(targettype, wordvalue = value, position = position) - if (targettype == DataType.UWORD && value in 0..65535) - return LiteralValue(targettype, wordvalue = value, position = position) - } - } - in StringDatatypes -> { - if(targettype in StringDatatypes) - return this - } - else -> {} - } - return null // invalid type conversion from $this to $targettype - } - - fun addToHeap(heap: HeapValues) { - if(heapId==null) { - if (strvalue != null) { - heapId = heap.addString(type, strvalue) - } - else if (arrayvalue!=null) { - if(arrayvalue.any {it is AddressOf}) { - val intArrayWithAddressOfs = arrayvalue.map { - when (it) { - is AddressOf -> IntegerOrAddressOf(null, it) - is LiteralValue -> IntegerOrAddressOf(it.asIntegerValue, null) - else -> throw FatalAstException("invalid datatype in array") - } - } - heapId = heap.addIntegerArray(type, intArrayWithAddressOfs.toTypedArray()) - } else { - val valuesInArray = arrayvalue.map { (it as LiteralValue).asNumericValue!! } - if(type==DataType.ARRAY_F) { - val doubleArray = valuesInArray.map { it.toDouble() }.toDoubleArray() - heapId = heap.addDoublesArray(doubleArray) - } else { - val integerArray = valuesInArray.map { it.toInt() } - heapId = heap.addIntegerArray(type, integerArray.map { IntegerOrAddressOf(it, null) }.toTypedArray()) - } - } - } - } - } -} - - -class RangeExpr(var from: IExpression, - var to: IExpression, - var step: IExpression, - override val position: Position) : IExpression { - override lateinit var parent: Node - - override fun linkParents(parent: Node) { - this.parent = parent - from.linkParents(this) - to.linkParents(this) - step.linkParents(this) - } - - override fun constValue(program: Program): LiteralValue? = null - override fun process(processor: IAstProcessor) = processor.process(this) - override fun referencesIdentifier(name: String): Boolean = from.referencesIdentifier(name) || to.referencesIdentifier(name) - override fun inferType(program: Program): DataType? { - val fromDt=from.inferType(program) - val toDt=to.inferType(program) - return when { - fromDt==null || toDt==null -> null - fromDt==DataType.UBYTE && toDt==DataType.UBYTE -> DataType.UBYTE - fromDt==DataType.UWORD && toDt==DataType.UWORD -> DataType.UWORD - fromDt==DataType.STR && toDt==DataType.STR -> DataType.STR - fromDt==DataType.STR_S && toDt==DataType.STR_S -> DataType.STR_S - fromDt==DataType.WORD || toDt==DataType.WORD -> DataType.WORD - fromDt==DataType.BYTE || toDt==DataType.BYTE -> DataType.BYTE - else -> DataType.UBYTE - } - } - override fun toString(): String { - return "RangeExpr(from $from, to $to, step $step, pos=$position)" - } - - fun size(): Int? { - val fromLv = (from as? LiteralValue) - val toLv = (to as? LiteralValue) - if(fromLv==null || toLv==null) - return null - return toConstantIntegerRange()?.count() - } - - fun toConstantIntegerRange(): IntProgression? { - val fromLv = from as? LiteralValue - val toLv = to as? LiteralValue - if(fromLv==null || toLv==null) - return null // non-constant range - val fromVal: Int - val toVal: Int - if(fromLv.isString && toLv.isString) { - // string range -> int range over petscii values - fromVal = Petscii.encodePetscii(fromLv.strvalue!!, true)[0].toInt() - toVal = Petscii.encodePetscii(toLv.strvalue!!, true)[0].toInt() - } else { - // integer range - fromVal = (from as LiteralValue).asIntegerValue!! - toVal = (to as LiteralValue).asIntegerValue!! - } - val stepVal = (step as? LiteralValue)?.asIntegerValue ?: 1 - return when { - fromVal <= toVal -> when { - stepVal <= 0 -> IntRange.EMPTY - stepVal == 1 -> fromVal..toVal - else -> fromVal..toVal step stepVal - } - else -> when { - stepVal >= 0 -> IntRange.EMPTY - stepVal == -1 -> fromVal downTo toVal - else -> fromVal downTo toVal step abs(stepVal) - } - } - } -} - - -class RegisterExpr(val register: Register, override val position: Position) : IExpression { - override lateinit var parent: Node - - override fun linkParents(parent: Node) { - this.parent = parent - } - - override fun constValue(program: Program): LiteralValue? = null - override fun process(processor: IAstProcessor) = this - override fun referencesIdentifier(name: String): Boolean = false - override fun toString(): String { - return "RegisterExpr(register=$register, pos=$position)" - } - - override fun inferType(program: Program) = DataType.UBYTE -} - - -data class IdentifierReference(val nameInSource: List, override val position: Position) : IExpression { - override lateinit var parent: Node - - fun targetStatement(namespace: INameScope) = - if(nameInSource.size==1 && nameInSource[0] in BuiltinFunctions) - BuiltinFunctionStatementPlaceholder(nameInSource[0], position) - else - namespace.lookup(nameInSource, this) - - fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl - fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine - - override fun linkParents(parent: Node) { - this.parent = parent - } - - override fun constValue(program: Program): LiteralValue? { - val node = program.namespace.lookup(nameInSource, this) - ?: throw UndefinedSymbolError(this) - val vardecl = node as? VarDecl - if(vardecl==null) { - return null - } else if(vardecl.type!=VarDeclType.CONST) { - return null - } - return vardecl.value?.constValue(program) - } - - override fun toString(): String { - return "IdentifierRef($nameInSource)" - } - - override fun process(processor: IAstProcessor) = processor.process(this) - override fun referencesIdentifier(name: String): Boolean = nameInSource.last() == name // @todo is this correct all the time? - - override fun inferType(program: Program): DataType? { - val targetStmt = targetStatement(program.namespace) - if(targetStmt is VarDecl) { - return targetStmt.datatype - } else { - throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position") - } - } - - fun heapId(namespace: INameScope): Int { - val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolError(this) - return ((node as? VarDecl)?.value as? LiteralValue)?.heapId ?: throw FatalAstException("identifier is not on the heap: $this") - } -} - - -class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : IStatement { - override lateinit var parent: Node - override val expensiveToInline = false - - override fun linkParents(parent: Node) { - this.parent = parent - target.linkParents(this) - } - - override fun process(processor: IAstProcessor) = processor.process(this) - - override fun toString(): String { - return "PostIncrDecr(op: $operator, target: $target, pos=$position)" - } -} - - -class Jump(val address: Int?, - val identifier: IdentifierReference?, - val generatedLabel: String?, // used in code generation scenarios - override val position: Position) : IStatement { - override lateinit var parent: Node - override val expensiveToInline = false - - override fun linkParents(parent: Node) { - this.parent = parent - identifier?.linkParents(this) - } - - override fun process(processor: IAstProcessor) = processor.process(this) - - override fun toString(): String { - return "Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)" - } -} - - -class FunctionCall(override var target: IdentifierReference, - override var arglist: MutableList, - override val position: Position) : IExpression, IFunctionCall { - override lateinit var parent: Node - - override fun linkParents(parent: Node) { - this.parent = parent - target.linkParents(this) - arglist.forEach { it.linkParents(this) } - } - - override fun constValue(program: Program) = constValue(program, true) - - private fun constValue(program: Program, withDatatypeCheck: Boolean): LiteralValue? { - // if the function is a built-in function and the args are consts, should try to const-evaluate! - // lenghts of arrays and strings are constants that are determined at compile time! - if(target.nameInSource.size>1) return null - try { - var resultValue: LiteralValue? = null - val func = BuiltinFunctions[target.nameInSource[0]] - if(func!=null) { - val exprfunc = func.constExpressionFunc - if(exprfunc!=null) - resultValue = exprfunc(arglist, position, program) - else if(func.returntype==null) - throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used here because it doesn't return a value", position) - } - - if(withDatatypeCheck) { - val resultDt = this.inferType(program) - if(resultValue==null || resultDt == resultValue.type) - return resultValue - throw FatalAstException("evaluated const expression result value doesn't match expected datatype $resultDt, pos=$position") - } else { - return resultValue - } - } - catch(x: NotConstArgumentException) { - // const-evaluating the builtin function call failed. - return null - } - } - - override fun toString(): String { - return "FunctionCall(target=$target, pos=$position)" - } - - override fun process(processor: IAstProcessor) = processor.process(this) - override fun referencesIdentifier(name: String): Boolean = target.referencesIdentifier(name) || arglist.any{it.referencesIdentifier(name)} - - override fun inferType(program: Program): DataType? { - val constVal = constValue(program ,false) - if(constVal!=null) - return constVal.type - val stmt = target.targetStatement(program.namespace) ?: return null - when (stmt) { - is BuiltinFunctionStatementPlaceholder -> { - if(target.nameInSource[0] == "set_carry" || target.nameInSource[0]=="set_irqd" || - target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") { - return null // these have no return value - } - return builtinFunctionReturnType(target.nameInSource[0], this.arglist, program) - } - is Subroutine -> { - if(stmt.returntypes.isEmpty()) - return null // no return value - if(stmt.returntypes.size==1) - return stmt.returntypes[0] - return null // has multiple return types... so not a single resulting datatype possible - } - is Label -> return null - } - return null // calling something we don't recognise... - } -} - - -class FunctionCallStatement(override var target: IdentifierReference, - override var arglist: MutableList, - override val position: Position) : IStatement, IFunctionCall { - override lateinit var parent: Node - override val expensiveToInline - get() = arglist.any { it !is LiteralValue } - - override fun linkParents(parent: Node) { - this.parent = parent - target.linkParents(this) - arglist.forEach { it.linkParents(this) } - } - - override fun process(processor: IAstProcessor) = processor.process(this) - - override fun toString(): String { - return "FunctionCallStatement(target=$target, pos=$position)" - } -} - - -class InlineAssembly(val assembly: String, override val position: Position) : IStatement { - override lateinit var parent: Node - override val expensiveToInline = true - - override fun linkParents(parent: Node) { - this.parent = parent - } - - override fun process(processor: IAstProcessor) = processor.process(this) -} - - -data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean?) - -class AnonymousScope(override var statements: MutableList, - override val position: Position) : INameScope, IStatement { - override val name: String - override lateinit var parent: Node - override val expensiveToInline - get() = statements.any { it.expensiveToInline } - - init { - name = "" // make sure it's an invalid soruce code identifier so user source code can never produce it - 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 NopStatement(override val position:Position): IStatement { - override lateinit var parent: Node - override val expensiveToInline = false - - override fun linkParents(parent: Node) { - this.parent = parent - } - - override fun process(processor: IAstProcessor) = this -} - - -// the subroutine class covers both the normal user-defined subroutines, -// and also the predefined/ROM/register-based subroutines. -// (multiple return types can only occur for the latter type) -class Subroutine(override val name: String, - val parameters: List, - val returntypes: List, - val asmParameterRegisters: List, - val asmReturnvaluesRegisters: List, - val asmClobbers: Set, - val asmAddress: Int?, - val isAsmSubroutine: Boolean, - override var statements: MutableList, - override val position: Position) : IStatement, INameScope { - - var keepAlways: Boolean = false - override val expensiveToInline - get() = statements.any { it.expensiveToInline } - - override lateinit var parent: Node - val calledBy = mutableListOf() - val calls = mutableSetOf() - - val scopedname: String by lazy { makeScopedName(name) } - - override fun linkParents(parent: Node) { - this.parent = parent - parameters.forEach { it.linkParents(this) } - statements.forEach { it.linkParents(this) } - } - - override fun process(processor: IAstProcessor) = processor.process(this) - - override fun toString(): String { - return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)" - } - - fun amountOfRtsInAsm(): Int = statements - .asSequence() - .filter { it is InlineAssembly } - .map { (it as InlineAssembly).assembly } - .count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it } - - val canBeAsmSubroutine =false // TODO disabled for now, see below about problem with converting to asm subroutine -// !isAsmSubroutine -// && ((parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE, DataType.WORD, DataType.UWORD)) -// || (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE })) - - fun intoAsmSubroutine(): Subroutine { - // TODO turn subroutine into asm calling convention. Requires rethinking of how parameters are handled (conflicts with local vardefs now, see AstIdentifierChecker...) - return this // TODO - -// println("TO ASM $this") // TODO -// val paramregs = if (parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE)) -// listOf(RegisterOrStatusflag(RegisterOrPair.Y, null, null)) -// else if (parameters.size == 1 && parameters[0].type in setOf(DataType.WORD, DataType.UWORD)) -// listOf(RegisterOrStatusflag(RegisterOrPair.AY, null, null)) -// else if (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE }) -// listOf(RegisterOrStatusflag(RegisterOrPair.A, null, null), RegisterOrStatusflag(RegisterOrPair.Y, null, null)) -// else throw FatalAstException("cannot convert subroutine to asm parameters") -// -// val asmsub=Subroutine( -// name, -// parameters, -// returntypes, -// paramregs, -// emptyList(), -// emptySet(), -// null, -// true, -// statements, -// position -// ) -// asmsub.linkParents(parent) -// return asmsub - } -} - - -open class SubroutineParameter(val name: String, - val type: DataType, - override val position: Position) : Node { - override lateinit var parent: Node - - override fun linkParents(parent: Node) { - this.parent = parent - } -} - -class IfStatement(var condition: IExpression, - var truepart: AnonymousScope, - var elsepart: AnonymousScope, - override val position: Position) : IStatement { - override lateinit var parent: Node - override val expensiveToInline: Boolean - get() = truepart.expensiveToInline || elsepart.expensiveToInline - - override fun linkParents(parent: Node) { - this.parent = parent - condition.linkParents(this) - truepart.linkParents(this) - elsepart.linkParents(this) - } - - override fun process(processor: IAstProcessor): IStatement = processor.process(this) -} - - -class BranchStatement(var condition: BranchCondition, - var truepart: AnonymousScope, - var elsepart: AnonymousScope, - override val position: Position) : IStatement { - override lateinit var parent: Node - override val expensiveToInline: Boolean - get() = truepart.expensiveToInline || elsepart.expensiveToInline - - override fun linkParents(parent: Node) { - this.parent = parent - truepart.linkParents(this) - elsepart.linkParents(this) - } - - override fun process(processor: IAstProcessor): IStatement = processor.process(this) -} - - -class ForLoop(val loopRegister: Register?, - val decltype: DataType?, - val zeropage: Boolean, - val loopVar: IdentifierReference?, - var iterable: IExpression, - var body: AnonymousScope, - override val position: Position) : IStatement { - override lateinit var parent: Node - override val expensiveToInline = true - - override fun linkParents(parent: Node) { - this.parent=parent - loopVar?.linkParents(if(decltype==null) this else body) - iterable.linkParents(this) - body.linkParents(this) - } - - override fun process(processor: IAstProcessor) = processor.process(this) - - override fun toString(): String { - return "ForLoop(loopVar: $loopVar, loopReg: $loopRegister, iterable: $iterable, pos=$position)" - } - - companion object { - const val iteratorLoopcounterVarname = "prog8forloopcounter" - } -} - - -class WhileLoop(var condition: IExpression, - var body: AnonymousScope, - override val position: Position) : IStatement { - override lateinit var parent: Node - override val expensiveToInline = true - - override fun linkParents(parent: Node) { - this.parent = parent - condition.linkParents(this) - body.linkParents(this) - } - - override fun process(processor: IAstProcessor): IStatement = processor.process(this) -} - - -class RepeatLoop(var body: AnonymousScope, - var untilCondition: IExpression, - override val position: Position) : IStatement { - override lateinit var parent: Node - override val expensiveToInline = true - - override fun linkParents(parent: Node) { - this.parent = parent - untilCondition.linkParents(this) - body.linkParents(this) - } - - override fun process(processor: IAstProcessor): IStatement = processor.process(this) -} - - -/***************** Antlr Extension methods to create AST ****************/ - -fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module { - val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name - return Module(nameWithoutSuffix, modulestatement().asSequence().map { it.toAst(isLibrary) }.toMutableList(), toPosition(), isLibrary, source) -} - - -private fun ParserRuleContext.toPosition() : Position { - val customTokensource = this.start.tokenSource as? CustomLexer - val filename = - when { - customTokensource!=null -> customTokensource.modulePath.fileName.toString() - start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@" - else -> File(start.inputStream.sourceName).name - } - // note: be ware of TAB characters in the source text, they count as 1 column... - return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine+stop.text.length) -} - - -private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : IStatement { - val directive = directive()?.toAst() - if(directive!=null) return directive - - val block = block()?.toAst(isInLibrary) - if(block!=null) return block - - throw FatalAstException(text) -} - - -private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : IStatement = - Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, toPosition()) - - -private fun prog8Parser.Statement_blockContext.toAst(): MutableList = - statement().asSequence().map { it.toAst() }.toMutableList() - - -private fun prog8Parser.StatementContext.toAst() : IStatement { - vardecl()?.let { - return VarDecl(VarDeclType.VAR, - it.datatype().toAst(), - it.ZEROPAGE()!=null, - it.arrayindex()?.toAst(), - it.identifier().text, - null, - it.ARRAYSIG()!=null || it.arrayindex()!=null, - false, - it.toPosition()) - } - - varinitializer()?.let { - val vd = it.vardecl() - return VarDecl(VarDeclType.VAR, - vd.datatype().toAst(), - vd.ZEROPAGE()!=null, - vd.arrayindex()?.toAst(), - vd.identifier().text, - it.expression().toAst(), - vd.ARRAYSIG()!=null || vd.arrayindex()!=null, - false, - it.toPosition()) - } - - constdecl()?.let { - val cvarinit = it.varinitializer() - val vd = cvarinit.vardecl() - return VarDecl(VarDeclType.CONST, - vd.datatype().toAst(), - vd.ZEROPAGE()!=null, - vd.arrayindex()?.toAst(), - vd.identifier().text, - cvarinit.expression().toAst(), - vd.ARRAYSIG()!=null || vd.arrayindex()!=null, - false, - cvarinit.toPosition()) - } - - memoryvardecl()?.let { - val mvarinit = it.varinitializer() - val vd = mvarinit.vardecl() - return VarDecl(VarDeclType.MEMORY, - vd.datatype().toAst(), - vd.ZEROPAGE()!=null, - vd.arrayindex()?.toAst(), - vd.identifier().text, - mvarinit.expression().toAst(), - vd.ARRAYSIG()!=null || vd.arrayindex()!=null, - false, - mvarinit.toPosition()) - } - - assignment()?.let { - return Assignment(it.assign_targets().toAst(), null, it.expression().toAst(), it.toPosition()) - } - - augassignment()?.let { - return Assignment(listOf(it.assign_target().toAst()), - it.operator.text, - it.expression().toAst(), - it.toPosition()) - } - - postincrdecr()?.let { - return PostIncrDecr(it.assign_target().toAst(), it.operator.text, it.toPosition()) - } - - val directive = directive()?.toAst() - if(directive!=null) return directive - - val label = labeldef()?.toAst() - if(label!=null) return label - - val jump = unconditionaljump()?.toAst() - if(jump!=null) return jump - - val fcall = functioncall_stmt()?.toAst() - if(fcall!=null) return fcall - - val ifstmt = if_stmt()?.toAst() - if(ifstmt!=null) return ifstmt - - val returnstmt = returnstmt()?.toAst() - if(returnstmt!=null) return returnstmt - - val sub = subroutine()?.toAst() - if(sub!=null) return sub - - val asm = inlineasm()?.toAst() - if(asm!=null) return asm - - val branchstmt = branch_stmt()?.toAst() - if(branchstmt!=null) return branchstmt - - val forloop = forloop()?.toAst() - if(forloop!=null) return forloop - - val repeatloop = repeatloop()?.toAst() - if(repeatloop!=null) return repeatloop - - val whileloop = whileloop()?.toAst() - if(whileloop!=null) return whileloop - - val breakstmt = breakstmt()?.toAst() - if(breakstmt!=null) return breakstmt - - val continuestmt = continuestmt()?.toAst() - if(continuestmt!=null) return continuestmt - - val asmsubstmt = asmsubroutine()?.toAst() - if(asmsubstmt!=null) return asmsubstmt - - throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text") -} - -private fun prog8Parser.Assign_targetsContext.toAst(): List = assign_target().map { it.toAst() } - - -private fun prog8Parser.AsmsubroutineContext.toAst(): IStatement { - val name = identifier().text - val address = asmsub_address()?.address?.toAst()?.number?.toInt() - val params = asmsub_params()?.toAst() ?: emptyList() - val returns = asmsub_returns()?.toAst() ?: emptyList() - val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) } - val normalReturnvalues = returns.map { it.type } - val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) } - val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) } - val clobbers = clobber()?.toAst() ?: emptySet() - val statements = statement_block()?.toAst() ?: mutableListOf() - return Subroutine(name, normalParameters, normalReturnvalues, - paramRegisters, returnRegisters, clobbers, address, true, statements, toPosition()) -} - -private class AsmSubroutineParameter(name: String, - type: DataType, - val registerOrPair: RegisterOrPair?, - val statusflag: Statusflag?, - val stack: Boolean, - position: Position) : SubroutineParameter(name, type, position) - -private class AsmSubroutineReturn(val type: DataType, - val registerOrPair: RegisterOrPair?, - val statusflag: Statusflag?, - val stack: Boolean, - val position: Position) - -private fun prog8Parser.ClobberContext.toAst(): Set - = this.register().asSequence().map { it.toAst() }.toSet() - - -private fun prog8Parser.Asmsub_returnsContext.toAst(): List - = asmsub_return().map { AsmSubroutineReturn(it.datatype().toAst(), it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) } - -private fun prog8Parser.Asmsub_paramsContext.toAst(): List - = asmsub_param().map { AsmSubroutineParameter(it.vardecl().identifier().text, it.vardecl().datatype().toAst(), - it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) } - - -private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text) - - -private fun prog8Parser.Functioncall_stmtContext.toAst(): IStatement { - val location = scoped_identifier().toAst() - return if(expression_list() == null) - FunctionCallStatement(location, mutableListOf(), toPosition()) - else - FunctionCallStatement(location, expression_list().toAst().toMutableList(), toPosition()) -} - - -private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall { - val location = scoped_identifier().toAst() - return if(expression_list() == null) - FunctionCall(location, mutableListOf(), toPosition()) - else - FunctionCall(location, expression_list().toAst().toMutableList(), toPosition()) -} - - -private fun prog8Parser.InlineasmContext.toAst() = - InlineAssembly(INLINEASMBLOCK().text, toPosition()) - - -private fun prog8Parser.ReturnstmtContext.toAst() : Return { - val values = expression_list() - return Return(values?.toAst() ?: emptyList(), toPosition()) -} - -private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump { - val address = integerliteral()?.toAst()?.number?.toInt() - val identifier = scoped_identifier()?.toAst() - return Jump(address, identifier, null, toPosition()) -} - - -private fun prog8Parser.LabeldefContext.toAst(): IStatement = - Label(children[0].text, toPosition()) - - -private fun prog8Parser.SubroutineContext.toAst() : Subroutine { - return Subroutine(identifier().text, - sub_params()?.toAst() ?: emptyList(), - sub_return_part()?.toAst() ?: emptyList(), - emptyList(), - emptyList(), - emptySet(), - null, - false, - statement_block()?.toAst() ?: mutableListOf(), - toPosition()) -} - -private fun prog8Parser.Sub_return_partContext.toAst(): List { - val returns = sub_returns() ?: return emptyList() - return returns.datatype().map { it.toAst() } -} - - -private fun prog8Parser.Sub_paramsContext.toAst(): List = - vardecl().map { - SubroutineParameter(it.identifier().text, it.datatype().toAst(), it.toPosition()) - } - - -private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget { - val register = register()?.toAst() - val identifier = scoped_identifier() - return when { - register!=null -> AssignTarget(register, null, null, null, toPosition()) - identifier!=null -> AssignTarget(null, identifier.toAst(), null, null, toPosition()) - arrayindexed()!=null -> AssignTarget(null, null, arrayindexed().toAst(), null, toPosition()) - directmemory()!=null -> AssignTarget(null, null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition()) - else -> AssignTarget(null, scoped_identifier()?.toAst(), null, null, toPosition()) - } -} - -private fun prog8Parser.RegisterContext.toAst() = Register.valueOf(text.toUpperCase()) - -private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase()) - -private fun prog8Parser.RegisterorpairContext.toAst() = RegisterOrPair.valueOf(text.toUpperCase()) - - -private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex = - ArrayIndex(expression().toAst(), toPosition()) - - -private fun prog8Parser.DirectiveContext.toAst() : Directive = - Directive(directivename.text, directivearg().map { it.toAst() }, toPosition()) - - -private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg = - DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition()) - - -private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral { - fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral { - val integer: Int - var datatype = DataType.UBYTE - when (radix) { - 10 -> { - integer = try { - text.toInt() - } catch(x: NumberFormatException) { - throw AstException("${toPosition()} invalid decimal literal ${x.message}") - } - datatype = when(integer) { - in 0..255 -> DataType.UBYTE - in -128..127 -> DataType.BYTE - in 0..65535 -> DataType.UWORD - in -32768..32767 -> DataType.WORD - else -> DataType.FLOAT - } - } - 2 -> { - if(text.length>8) - datatype = DataType.UWORD - try { - integer = text.toInt(2) - } catch(x: NumberFormatException) { - throw AstException("${toPosition()} invalid binary literal ${x.message}") - } - } - 16 -> { - if(text.length>2) - datatype = DataType.UWORD - try { - integer = text.toInt(16) - } catch(x: NumberFormatException) { - throw AstException("${toPosition()} invalid hexadecimal literal ${x.message}") - } - } - else -> throw FatalAstException("invalid radix") - } - return NumericLiteral(integer, if(forceWord) DataType.UWORD else datatype) - } - val terminal: TerminalNode = children[0] as TerminalNode - val integerPart = this.intpart.text - return when (terminal.symbol.type) { - prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10, wordsuffix()!=null) - prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16, wordsuffix()!=null) - prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2, wordsuffix()!=null) - else -> throw FatalAstException(terminal.text) - } -} - - -private fun prog8Parser.ExpressionContext.toAst() : IExpression { - - val litval = literalvalue() - if(litval!=null) { - val booleanlit = litval.booleanliteral()?.toAst() - return if(booleanlit!=null) { - LiteralValue.fromBoolean(booleanlit, litval.toPosition()) - } - else { - val intLit = litval.integerliteral()?.toAst() - when { - intLit!=null -> when(intLit.datatype) { - DataType.UBYTE -> LiteralValue(DataType.UBYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition()) - DataType.BYTE -> LiteralValue(DataType.BYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition()) - DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = intLit.number.toInt(), position = litval.toPosition()) - DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = intLit.number.toInt(), position = litval.toPosition()) - DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue= intLit.number.toDouble(), position = litval.toPosition()) - else -> throw FatalAstException("invalid datatype for numeric literal") - } - litval.floatliteral()!=null -> LiteralValue(DataType.FLOAT, floatvalue = litval.floatliteral().toAst(), position = litval.toPosition()) - litval.stringliteral()!=null -> LiteralValue(DataType.STR, strvalue = unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition()) - litval.charliteral()!=null -> { - try { - LiteralValue(DataType.UBYTE, bytevalue = Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], position = litval.toPosition()) - } catch (ce: CharConversionException) { - throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition()) - } - } - litval.arrayliteral()!=null -> { - val array = litval.arrayliteral()?.toAst() - // the actual type of the arraysize can not yet be determined here (missing namespace & heap) - // the ConstantFolder takes care of that and converts the type if needed. - LiteralValue(DataType.ARRAY_UB, arrayvalue = array, position = litval.toPosition()) - } - else -> throw FatalAstException("invalid parsed literal") - } - } - } - - if(register()!=null) - return RegisterExpr(register().toAst(), register().toPosition()) - - if(scoped_identifier()!=null) - return scoped_identifier().toAst() - - if(bop!=null) - return BinaryExpression(left.toAst(), bop.text, right.toAst(), toPosition()) - - if(prefix!=null) - return PrefixExpression(prefix.text, expression(0).toAst(), toPosition()) - - val funcall = functioncall()?.toAst() - if(funcall!=null) return funcall - - if (rangefrom!=null && rangeto!=null) { - val step = rangestep?.toAst() ?: LiteralValue(DataType.UBYTE, 1, position = toPosition()) - return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition()) - } - - if(childCount==3 && children[0].text=="(" && children[2].text==")") - return expression(0).toAst() // expression within ( ) - - if(arrayindexed()!=null) - return arrayindexed().toAst() - - if(typecast()!=null) - return TypecastExpression(expression(0).toAst(), typecast().datatype().toAst(), false, toPosition()) - - if(directmemory()!=null) - return DirectMemoryRead(directmemory().expression().toAst(), toPosition()) - - if(addressof()!=null) - return AddressOf(addressof().scoped_identifier().toAst(), toPosition()) - - throw FatalAstException(text) -} - - -private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression { - return ArrayIndexedExpression(scoped_identifier().toAst(), - arrayindex().toAst(), - toPosition()) -} - - -private fun prog8Parser.Expression_listContext.toAst() = expression().map{ it.toAst() } - - -private fun prog8Parser.IdentifierContext.toAst() : IdentifierReference = - IdentifierReference(listOf(text), toPosition()) - - -private fun prog8Parser.Scoped_identifierContext.toAst() : IdentifierReference = - IdentifierReference(NAME().map { it.text }, toPosition()) - - -private fun prog8Parser.FloatliteralContext.toAst() = text.toDouble() - - -private fun prog8Parser.BooleanliteralContext.toAst() = when(text) { - "true" -> true - "false" -> false - else -> throw FatalAstException(text) -} - - -private fun prog8Parser.ArrayliteralContext.toAst() : Array = - expression().map { it.toAst() }.toTypedArray() - - -private fun prog8Parser.If_stmtContext.toAst(): IfStatement { - val condition = expression().toAst() - 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(): MutableList { - return statement_block()?.toAst() ?: mutableListOf(statement().toAst()) -} - - -private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement { - val branchcondition = branchcondition().toAst() - 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()) - - -private fun prog8Parser.ForloopContext.toAst(): ForLoop { - val loopregister = register()?.toAst() - val datatype = datatype()?.toAst() - val zeropage = ZEROPAGE()!=null - val loopvar = identifier()?.toAst() - val iterable = expression()!!.toAst() - val scope = - if(statement()!=null) - AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition()) - else - AnonymousScope(statement_block().toAst(), statement_block().toPosition()) - return ForLoop(loopregister, datatype, zeropage, loopvar, iterable, scope, toPosition()) -} - - -private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition()) - -private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition()) - - -private fun prog8Parser.WhileloopContext.toAst(): WhileLoop { - val condition = expression().toAst() - 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() ?: mutableListOf(statement().toAst()) - val scope = AnonymousScope(statements, statement_block()?.toPosition() ?: statement().toPosition()) - return RepeatLoop(scope, untilCondition, toPosition()) -} - - -internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r") - -internal fun unescape(str: String, position: Position): String { - val result = mutableListOf() - val iter = str.iterator() - while(iter.hasNext()) { - val c = iter.nextChar() - if(c=='\\') { - val ec = iter.nextChar() - result.add(when(ec) { - '\\' -> '\\' - 'n' -> '\n' - 'r' -> '\r' - '"' -> '"' - 'u' -> { - "${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar() - } - else -> throw SyntaxError("invalid escape char in string: \\$ec", position) - }) - } else { - result.add(c) - } - } - return result.joinToString("") -} diff --git a/compiler/src/prog8/ast/AstRecursionChecker.kt b/compiler/src/prog8/ast/AstRecursionChecker.kt deleted file mode 100644 index a27616568..000000000 --- a/compiler/src/prog8/ast/AstRecursionChecker.kt +++ /dev/null @@ -1,120 +0,0 @@ -package prog8.ast - -/** - * Checks for the occurrence of recursive subroutine calls - */ - -internal fun Program.checkRecursion() { - val checker = AstRecursionChecker(namespace) - checker.process(this) - printErrors(checker.result(), name) -} - - -private class DirectedGraph { - private val graph = mutableMapOf>() - private var uniqueVertices = mutableSetOf() - val numVertices : Int - get() = uniqueVertices.size - - fun add(from: VT, to: VT) { - var targets = graph[from] - if(targets==null) { - targets = mutableSetOf() - graph[from] = targets - } - targets.add(to) - uniqueVertices.add(from) - uniqueVertices.add(to) - } - - fun print() { - println("#vertices: $numVertices") - graph.forEach { (from, to) -> - println("$from CALLS:") - to.forEach { println(" $it") } - } - val cycle = checkForCycle() - if(cycle.isNotEmpty()) { - println("CYCLIC! $cycle") - } - } - - fun checkForCycle(): MutableList { - val visited = uniqueVertices.associateWith { false }.toMutableMap() - val recStack = uniqueVertices.associateWith { false }.toMutableMap() - val cycle = mutableListOf() - for(node in uniqueVertices) { - if(isCyclicUntil(node, visited, recStack, cycle)) - return cycle - } - return mutableListOf() - } - - private fun isCyclicUntil(node: VT, - visited: MutableMap, - recStack: MutableMap, - cycleNodes: MutableList): Boolean { - - if(recStack[node]==true) return true - if(visited[node]==true) return false - - // mark current node as visited and add to recursion stack - visited[node] = true - recStack[node] = true - - // recurse for all neighbours - val neighbors = graph[node] - if(neighbors!=null) { - for (neighbour in neighbors) { - if (isCyclicUntil(neighbour, visited, recStack, cycleNodes)) { - cycleNodes.add(node) - return true - } - } - } - - // pop node from recursion stack - recStack[node] = false - return false - } -} - - -private class AstRecursionChecker(private val namespace: INameScope) : IAstProcessor { - private val callGraph = DirectedGraph() - - internal fun result(): List { - val cycle = callGraph.checkForCycle() - if(cycle.isEmpty()) - return emptyList() - val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" } - return listOf(AstException("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain")) - } - - override fun process(functionCallStatement: FunctionCallStatement): IStatement { - val scope = functionCallStatement.definingScope() - val targetStatement = functionCallStatement.target.targetStatement(namespace) - if(targetStatement!=null) { - val targetScope = when (targetStatement) { - is Subroutine -> targetStatement - else -> targetStatement.definingScope() - } - callGraph.add(scope, targetScope) - } - return super.process(functionCallStatement) - } - - override fun process(functionCall: FunctionCall): IExpression { - val scope = functionCall.definingScope() - val targetStatement = functionCall.target.targetStatement(namespace) - if(targetStatement!=null) { - val targetScope = when (targetStatement) { - is Subroutine -> targetStatement - else -> targetStatement.definingScope() - } - callGraph.add(scope, targetScope) - } - return super.process(functionCall) - } -} diff --git a/compiler/src/prog8/ast/AstToplevel.kt b/compiler/src/prog8/ast/AstToplevel.kt new file mode 100644 index 000000000..94e761af3 --- /dev/null +++ b/compiler/src/prog8/ast/AstToplevel.kt @@ -0,0 +1,92 @@ +package prog8.ast + +import prog8.ast.base.FatalAstException +import prog8.ast.base.NameError +import prog8.ast.base.ParentSentinel +import prog8.ast.base.Position +import prog8.ast.statements.Block +import prog8.ast.statements.Label +import prog8.ast.statements.Subroutine +import prog8.ast.statements.VarDecl +import prog8.compiler.HeapValues +import prog8.functions.BuiltinFunctions +import java.nio.file.Path + + +/*********** Everything starts from here, the Program; zero or more modules *************/ + +class Program(val name: String, val modules: MutableList) { + val namespace = GlobalNamespace(modules) + val heap = HeapValues() + + val loadAddress: Int + get() = modules.first().loadAddress + + fun entrypoint(): Subroutine? { + val mainBlocks = modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block } + if(mainBlocks.size > 1) + throw FatalAstException("more than one 'main' block") + return if(mainBlocks.isEmpty()) { + null + } else { + mainBlocks[0].subScopes()["start"] as Subroutine? + } + } +} + +class Module(override val name: String, + override var statements: MutableList, + override val position: Position, + val isLibraryModule: Boolean, + val source: Path) : Node, INameScope { + override lateinit var parent: Node + lateinit var program: Program + val importedBy = mutableListOf() + val imports = mutableSetOf() + + var loadAddress: Int = 0 // can be set with the %address directive + + override fun linkParents(parent: Node) { + this.parent = parent + statements.forEach {it.linkParents(this)} + } + + override fun definingScope(): INameScope = program.namespace + + override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)" +} + +class GlobalNamespace(val modules: List): Node, INameScope { + override val name = "<<>>" + override val position = Position("<<>>", 0, 0, 0) + override val statements = mutableListOf() + override var parent: Node = ParentSentinel + + override fun linkParents(parent: Node) { + modules.forEach { it.linkParents(this) } + } + + override fun lookup(scopedName: List, localContext: Node): IStatement? { + if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) { + // builtin functions always exist, return a dummy localContext for them + val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position) + builtinPlaceholder.parent = ParentSentinel + return builtinPlaceholder + } + + val stmt = localContext.definingModule().lookup(scopedName, localContext) + return when (stmt) { + is Label, is VarDecl, is Block, is Subroutine -> stmt + null -> null + else -> throw NameError("wrong identifier target: $stmt", stmt.position) + } + } +} + +object BuiltinFunctionScopePlaceholder : INameScope { + override val name = "<>" + override val position = Position("<>", 0, 0, 0) + override var statements = mutableListOf() + override var parent: Node = ParentSentinel + override fun linkParents(parent: Node) {} +} diff --git a/compiler/src/prog8/ast/Interfaces.kt b/compiler/src/prog8/ast/Interfaces.kt new file mode 100644 index 000000000..10e1fff14 --- /dev/null +++ b/compiler/src/prog8/ast/Interfaces.kt @@ -0,0 +1,194 @@ +package prog8.ast + +import prog8.ast.base.* +import prog8.ast.expressions.* +import prog8.ast.processing.IAstProcessor +import prog8.ast.statements.* + +interface Node { + val position: Position + var parent: Node // will be linked correctly later (late init) + fun linkParents(parent: Node) + + fun definingModule(): Module { + if(this is Module) + return this + return findParentNode(this)!! + } + + fun definingSubroutine(): Subroutine? = findParentNode(this) + + fun definingScope(): INameScope { + val scope = findParentNode(this) + if(scope!=null) { + return scope + } + if(this is Label && this.name.startsWith("builtin::")) { + return BuiltinFunctionScopePlaceholder + } + if(this is GlobalNamespace) + return this + throw FatalAstException("scope missing from $this") + } +} + +interface IStatement : Node { + fun process(processor: IAstProcessor) : IStatement + fun makeScopedName(name: String): String { + // easy way out is to always return the full scoped name. + // it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now. + // and like this, we can cache the name even, + // like in a lazy property on the statement object itself (label, subroutine, vardecl) + val scope = mutableListOf() + var statementScope = this.parent + while(statementScope !is ParentSentinel && statementScope !is Module) { + if(statementScope is INameScope) { + scope.add(0, statementScope.name) + } + statementScope = statementScope.parent + } + if(name.isNotEmpty()) + scope.add(name) + return scope.joinToString(".") + } + + val expensiveToInline: Boolean + + fun definingBlock(): Block { + if(this is Block) + return this + return findParentNode(this)!! + } +} + +interface IFunctionCall { + var target: IdentifierReference + var arglist: MutableList +} + +interface INameScope { + val name: String + val position: Position + val statements: MutableList + val parent: Node + + fun linkParents(parent: Node) + + 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.containsCodeOrVars()) + subscopes[stmt.elsepart.name] = stmt.elsepart + } + is IfStatement -> { + subscopes[stmt.truepart.name] = stmt.truepart + if(stmt.elsepart.containsCodeOrVars()) + subscopes[stmt.elsepart.name] = stmt.elsepart + } + } + } + return subscopes + } + + fun getLabelOrVariable(name: String): IStatement? { + // TODO this is called A LOT and could perhaps be optimized a bit more, but adding a 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 allDefinedSymbols(): List> { + 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, localContext: Node) : IStatement? { + if(scopedName.size>1) { + // 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?.subScopes()?.get(name) + if(scope==null) + break + } + if(scope!=null) { + val result = scope.getLabelOrVariable(scopedName.last()) + if(result!=null) + return result + return scope.subScopes()[scopedName.last()] as IStatement? + } + } + 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.subScopes()[scopedName[0]] as IStatement? + if (subscope != null) + return subscope + // not found in this scope, look one higher up + statementScope = statementScope.parent + } + return null + } + } + + fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"} + fun containsNoCodeNorVars() = !containsCodeOrVars() + + fun remove(stmt: IStatement) { + if(!statements.remove(stmt)) + throw FatalAstException("stmt to remove wasn't found in scope") + } +} + +interface IExpression: Node { + fun constValue(program: Program): LiteralValue? + fun process(processor: IAstProcessor): IExpression + fun referencesIdentifier(name: String): Boolean + fun inferType(program: Program): DataType? + + infix fun isSameAs(other: IExpression): Boolean { + if(this===other) + return true + when(this) { + is RegisterExpr -> + return (other is RegisterExpr && other.register==register) + is IdentifierReference -> + return (other is IdentifierReference && other.nameInSource==nameInSource) + is PrefixExpression -> + return (other is PrefixExpression && other.operator==operator && other.expression isSameAs expression) + is BinaryExpression -> + return (other is BinaryExpression && other.operator==operator + && other.left isSameAs left + && other.right isSameAs right) + is ArrayIndexedExpression -> { + return (other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource + && other.arrayspec.index isSameAs arrayspec.index) + } + is LiteralValue -> return (other is LiteralValue && other==this) + } + return false + } +} diff --git a/compiler/src/prog8/ast/antlr/Antr2Kotlin.kt b/compiler/src/prog8/ast/antlr/Antr2Kotlin.kt new file mode 100644 index 000000000..20affd00b --- /dev/null +++ b/compiler/src/prog8/ast/antlr/Antr2Kotlin.kt @@ -0,0 +1,572 @@ +package prog8.ast.antlr + +import org.antlr.v4.runtime.IntStream +import org.antlr.v4.runtime.ParserRuleContext +import org.antlr.v4.runtime.tree.TerminalNode +import prog8.ast.* +import prog8.ast.base.* +import prog8.ast.expressions.* +import prog8.ast.statements.* +import java.io.CharConversionException +import java.io.File +import java.nio.file.Path +import prog8.compiler.target.c64.Petscii +import prog8.parser.CustomLexer +import prog8.parser.prog8Parser + + +/***************** Antlr Extension methods to create AST ****************/ + +private data class NumericLiteral(val number: Number, val datatype: DataType) + + +fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module { + val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name + return Module(nameWithoutSuffix, modulestatement().asSequence().map { it.toAst(isLibrary) }.toMutableList(), toPosition(), isLibrary, source) +} + + +private fun ParserRuleContext.toPosition() : Position { + val customTokensource = this.start.tokenSource as? CustomLexer + val filename = + when { + customTokensource!=null -> customTokensource.modulePath.fileName.toString() + start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@" + else -> File(start.inputStream.sourceName).name + } + // note: be ware of TAB characters in the source text, they count as 1 column... + return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length) +} + + +private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : IStatement { + val directive = directive()?.toAst() + if(directive!=null) return directive + + val block = block()?.toAst(isInLibrary) + if(block!=null) return block + + throw FatalAstException(text) +} + + +private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : IStatement = + Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, toPosition()) + + +private fun prog8Parser.Statement_blockContext.toAst(): MutableList = + statement().asSequence().map { it.toAst() }.toMutableList() + + +private fun prog8Parser.StatementContext.toAst() : IStatement { + vardecl()?.let { + return VarDecl(VarDeclType.VAR, + it.datatype().toAst(), + it.ZEROPAGE() != null, + it.arrayindex()?.toAst(), + it.identifier().text, + null, + it.ARRAYSIG() != null || it.arrayindex() != null, + false, + it.toPosition()) + } + + varinitializer()?.let { + val vd = it.vardecl() + return VarDecl(VarDeclType.VAR, + vd.datatype().toAst(), + vd.ZEROPAGE() != null, + vd.arrayindex()?.toAst(), + vd.identifier().text, + it.expression().toAst(), + vd.ARRAYSIG() != null || vd.arrayindex() != null, + false, + it.toPosition()) + } + + constdecl()?.let { + val cvarinit = it.varinitializer() + val vd = cvarinit.vardecl() + return VarDecl(VarDeclType.CONST, + vd.datatype().toAst(), + vd.ZEROPAGE() != null, + vd.arrayindex()?.toAst(), + vd.identifier().text, + cvarinit.expression().toAst(), + vd.ARRAYSIG() != null || vd.arrayindex() != null, + false, + cvarinit.toPosition()) + } + + memoryvardecl()?.let { + val mvarinit = it.varinitializer() + val vd = mvarinit.vardecl() + return VarDecl(VarDeclType.MEMORY, + vd.datatype().toAst(), + vd.ZEROPAGE() != null, + vd.arrayindex()?.toAst(), + vd.identifier().text, + mvarinit.expression().toAst(), + vd.ARRAYSIG() != null || vd.arrayindex() != null, + false, + mvarinit.toPosition()) + } + + assignment()?.let { + return Assignment(it.assign_targets().toAst(), null, it.expression().toAst(), it.toPosition()) + } + + augassignment()?.let { + return Assignment(listOf(it.assign_target().toAst()), + it.operator.text, + it.expression().toAst(), + it.toPosition()) + } + + postincrdecr()?.let { + return PostIncrDecr(it.assign_target().toAst(), it.operator.text, it.toPosition()) + } + + val directive = directive()?.toAst() + if(directive!=null) return directive + + val label = labeldef()?.toAst() + if(label!=null) return label + + val jump = unconditionaljump()?.toAst() + if(jump!=null) return jump + + val fcall = functioncall_stmt()?.toAst() + if(fcall!=null) return fcall + + val ifstmt = if_stmt()?.toAst() + if(ifstmt!=null) return ifstmt + + val returnstmt = returnstmt()?.toAst() + if(returnstmt!=null) return returnstmt + + val sub = subroutine()?.toAst() + if(sub!=null) return sub + + val asm = inlineasm()?.toAst() + if(asm!=null) return asm + + val branchstmt = branch_stmt()?.toAst() + if(branchstmt!=null) return branchstmt + + val forloop = forloop()?.toAst() + if(forloop!=null) return forloop + + val repeatloop = repeatloop()?.toAst() + if(repeatloop!=null) return repeatloop + + val whileloop = whileloop()?.toAst() + if(whileloop!=null) return whileloop + + val breakstmt = breakstmt()?.toAst() + if(breakstmt!=null) return breakstmt + + val continuestmt = continuestmt()?.toAst() + if(continuestmt!=null) return continuestmt + + val asmsubstmt = asmsubroutine()?.toAst() + if(asmsubstmt!=null) return asmsubstmt + + throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text") +} + +private fun prog8Parser.Assign_targetsContext.toAst(): List = assign_target().map { it.toAst() } + + +private fun prog8Parser.AsmsubroutineContext.toAst(): IStatement { + val name = identifier().text + val address = asmsub_address()?.address?.toAst()?.number?.toInt() + val params = asmsub_params()?.toAst() ?: emptyList() + val returns = asmsub_returns()?.toAst() ?: emptyList() + val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) } + val normalReturnvalues = returns.map { it.type } + val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) } + val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) } + val clobbers = clobber()?.toAst() ?: emptySet() + val statements = statement_block()?.toAst() ?: mutableListOf() + return Subroutine(name, normalParameters, normalReturnvalues, + paramRegisters, returnRegisters, clobbers, address, true, statements, toPosition()) +} + +private class AsmSubroutineParameter(name: String, + type: DataType, + val registerOrPair: RegisterOrPair?, + val statusflag: Statusflag?, + val stack: Boolean, + position: Position) : SubroutineParameter(name, type, position) + +private class AsmSubroutineReturn(val type: DataType, + val registerOrPair: RegisterOrPair?, + val statusflag: Statusflag?, + val stack: Boolean, + val position: Position) + +private fun prog8Parser.ClobberContext.toAst(): Set + = this.register().asSequence().map { it.toAst() }.toSet() + + +private fun prog8Parser.Asmsub_returnsContext.toAst(): List + = asmsub_return().map { AsmSubroutineReturn(it.datatype().toAst(), it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) } + +private fun prog8Parser.Asmsub_paramsContext.toAst(): List + = asmsub_param().map { + AsmSubroutineParameter(it.vardecl().identifier().text, it.vardecl().datatype().toAst(), + it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) +} + + +private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text) + + +private fun prog8Parser.Functioncall_stmtContext.toAst(): IStatement { + val location = scoped_identifier().toAst() + return if(expression_list() == null) + FunctionCallStatement(location, mutableListOf(), toPosition()) + else + FunctionCallStatement(location, expression_list().toAst().toMutableList(), toPosition()) +} + + +private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall { + val location = scoped_identifier().toAst() + return if(expression_list() == null) + FunctionCall(location, mutableListOf(), toPosition()) + else + FunctionCall(location, expression_list().toAst().toMutableList(), toPosition()) +} + + +private fun prog8Parser.InlineasmContext.toAst() = + InlineAssembly(INLINEASMBLOCK().text, toPosition()) + + +private fun prog8Parser.ReturnstmtContext.toAst() : Return { + val values = expression_list() + return Return(values?.toAst() ?: emptyList(), toPosition()) +} + +private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump { + val address = integerliteral()?.toAst()?.number?.toInt() + val identifier = scoped_identifier()?.toAst() + return Jump(address, identifier, null, toPosition()) +} + + +private fun prog8Parser.LabeldefContext.toAst(): IStatement = + Label(children[0].text, toPosition()) + + +private fun prog8Parser.SubroutineContext.toAst() : Subroutine { + return Subroutine(identifier().text, + sub_params()?.toAst() ?: emptyList(), + sub_return_part()?.toAst() ?: emptyList(), + emptyList(), + emptyList(), + emptySet(), + null, + false, + statement_block()?.toAst() ?: mutableListOf(), + toPosition()) +} + +private fun prog8Parser.Sub_return_partContext.toAst(): List { + val returns = sub_returns() ?: return emptyList() + return returns.datatype().map { it.toAst() } +} + + +private fun prog8Parser.Sub_paramsContext.toAst(): List = + vardecl().map { + SubroutineParameter(it.identifier().text, it.datatype().toAst(), it.toPosition()) + } + + +private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget { + val register = register()?.toAst() + val identifier = scoped_identifier() + return when { + register!=null -> AssignTarget(register, null, null, null, toPosition()) + identifier!=null -> AssignTarget(null, identifier.toAst(), null, null, toPosition()) + arrayindexed()!=null -> AssignTarget(null, null, arrayindexed().toAst(), null, toPosition()) + directmemory()!=null -> AssignTarget(null, null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition()) + else -> AssignTarget(null, scoped_identifier()?.toAst(), null, null, toPosition()) + } +} + +private fun prog8Parser.RegisterContext.toAst() = Register.valueOf(text.toUpperCase()) + +private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase()) + +private fun prog8Parser.RegisterorpairContext.toAst() = RegisterOrPair.valueOf(text.toUpperCase()) + + +private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex = + ArrayIndex(expression().toAst(), toPosition()) + + +private fun prog8Parser.DirectiveContext.toAst() : Directive = + Directive(directivename.text, directivearg().map { it.toAst() }, toPosition()) + + +private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg = + DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition()) + + +private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral { + fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral { + val integer: Int + var datatype = DataType.UBYTE + when (radix) { + 10 -> { + integer = try { + text.toInt() + } catch(x: NumberFormatException) { + throw AstException("${toPosition()} invalid decimal literal ${x.message}") + } + datatype = when(integer) { + in 0..255 -> DataType.UBYTE + in -128..127 -> DataType.BYTE + in 0..65535 -> DataType.UWORD + in -32768..32767 -> DataType.WORD + else -> DataType.FLOAT + } + } + 2 -> { + if(text.length>8) + datatype = DataType.UWORD + try { + integer = text.toInt(2) + } catch(x: NumberFormatException) { + throw AstException("${toPosition()} invalid binary literal ${x.message}") + } + } + 16 -> { + if(text.length>2) + datatype = DataType.UWORD + try { + integer = text.toInt(16) + } catch(x: NumberFormatException) { + throw AstException("${toPosition()} invalid hexadecimal literal ${x.message}") + } + } + else -> throw FatalAstException("invalid radix") + } + return NumericLiteral(integer, if (forceWord) DataType.UWORD else datatype) + } + val terminal: TerminalNode = children[0] as TerminalNode + val integerPart = this.intpart.text + return when (terminal.symbol.type) { + prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10, wordsuffix()!=null) + prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16, wordsuffix()!=null) + prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2, wordsuffix()!=null) + else -> throw FatalAstException(terminal.text) + } +} + + +private fun prog8Parser.ExpressionContext.toAst() : IExpression { + + val litval = literalvalue() + if(litval!=null) { + val booleanlit = litval.booleanliteral()?.toAst() + return if(booleanlit!=null) { + LiteralValue.fromBoolean(booleanlit, litval.toPosition()) + } + else { + val intLit = litval.integerliteral()?.toAst() + when { + intLit!=null -> when(intLit.datatype) { + DataType.UBYTE -> LiteralValue(DataType.UBYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition()) + DataType.BYTE -> LiteralValue(DataType.BYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition()) + DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = intLit.number.toInt(), position = litval.toPosition()) + DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = intLit.number.toInt(), position = litval.toPosition()) + DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = intLit.number.toDouble(), position = litval.toPosition()) + else -> throw FatalAstException("invalid datatype for numeric literal") + } + litval.floatliteral()!=null -> LiteralValue(DataType.FLOAT, floatvalue = litval.floatliteral().toAst(), position = litval.toPosition()) + litval.stringliteral()!=null -> LiteralValue(DataType.STR, strvalue = unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition()) + litval.charliteral()!=null -> { + try { + LiteralValue(DataType.UBYTE, bytevalue = Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], position = litval.toPosition()) + } catch (ce: CharConversionException) { + throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition()) + } + } + litval.arrayliteral()!=null -> { + val array = litval.arrayliteral()?.toAst() + // the actual type of the arraysize can not yet be determined here (missing namespace & heap) + // the ConstantFolder takes care of that and converts the type if needed. + LiteralValue(DataType.ARRAY_UB, arrayvalue = array, position = litval.toPosition()) + } + else -> throw FatalAstException("invalid parsed literal") + } + } + } + + if(register()!=null) + return RegisterExpr(register().toAst(), register().toPosition()) + + if(scoped_identifier()!=null) + return scoped_identifier().toAst() + + if(bop!=null) + return BinaryExpression(left.toAst(), bop.text, right.toAst(), toPosition()) + + if(prefix!=null) + return PrefixExpression(prefix.text, expression(0).toAst(), toPosition()) + + val funcall = functioncall()?.toAst() + if(funcall!=null) return funcall + + if (rangefrom!=null && rangeto!=null) { + val step = rangestep?.toAst() ?: LiteralValue(DataType.UBYTE, 1, position = toPosition()) + return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition()) + } + + if(childCount==3 && children[0].text=="(" && children[2].text==")") + return expression(0).toAst() // expression within ( ) + + if(arrayindexed()!=null) + return arrayindexed().toAst() + + if(typecast()!=null) + return TypecastExpression(expression(0).toAst(), typecast().datatype().toAst(), false, toPosition()) + + if(directmemory()!=null) + return DirectMemoryRead(directmemory().expression().toAst(), toPosition()) + + if(addressof()!=null) + return AddressOf(addressof().scoped_identifier().toAst(), toPosition()) + + throw FatalAstException(text) +} + + +private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression { + return ArrayIndexedExpression(scoped_identifier().toAst(), + arrayindex().toAst(), + toPosition()) +} + + +private fun prog8Parser.Expression_listContext.toAst() = expression().map{ it.toAst() } + + +private fun prog8Parser.IdentifierContext.toAst() : IdentifierReference = + IdentifierReference(listOf(text), toPosition()) + + +private fun prog8Parser.Scoped_identifierContext.toAst() : IdentifierReference = + IdentifierReference(NAME().map { it.text }, toPosition()) + + +private fun prog8Parser.FloatliteralContext.toAst() = text.toDouble() + + +private fun prog8Parser.BooleanliteralContext.toAst() = when(text) { + "true" -> true + "false" -> false + else -> throw FatalAstException(text) +} + + +private fun prog8Parser.ArrayliteralContext.toAst() : Array = + expression().map { it.toAst() }.toTypedArray() + + +private fun prog8Parser.If_stmtContext.toAst(): IfStatement { + val condition = expression().toAst() + 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(): MutableList { + return statement_block()?.toAst() ?: mutableListOf(statement().toAst()) +} + + +private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement { + val branchcondition = branchcondition().toAst() + 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()) + + +private fun prog8Parser.ForloopContext.toAst(): ForLoop { + val loopregister = register()?.toAst() + val datatype = datatype()?.toAst() + val zeropage = ZEROPAGE()!=null + val loopvar = identifier()?.toAst() + val iterable = expression()!!.toAst() + val scope = + if(statement()!=null) + AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition()) + else + AnonymousScope(statement_block().toAst(), statement_block().toPosition()) + return ForLoop(loopregister, datatype, zeropage, loopvar, iterable, scope, toPosition()) +} + + +private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition()) + +private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition()) + + +private fun prog8Parser.WhileloopContext.toAst(): WhileLoop { + val condition = expression().toAst() + 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() ?: mutableListOf(statement().toAst()) + val scope = AnonymousScope(statements, statement_block()?.toPosition() + ?: statement().toPosition()) + return RepeatLoop(scope, untilCondition, toPosition()) +} + + +internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r") + +internal fun unescape(str: String, position: Position): String { + val result = mutableListOf() + val iter = str.iterator() + while(iter.hasNext()) { + val c = iter.nextChar() + if(c=='\\') { + val ec = iter.nextChar() + result.add(when(ec) { + '\\' -> '\\' + 'n' -> '\n' + 'r' -> '\r' + '"' -> '"' + 'u' -> { + "${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar() + } + else -> throw SyntaxError("invalid escape char in string: \\$ec", position) + }) + } else { + result.add(c) + } + } + return result.joinToString("") +} diff --git a/compiler/src/prog8/ast/base/Base.kt b/compiler/src/prog8/ast/base/Base.kt new file mode 100644 index 000000000..150927433 --- /dev/null +++ b/compiler/src/prog8/ast/base/Base.kt @@ -0,0 +1,130 @@ +package prog8.ast.base + +import prog8.ast.Node + +/**************************** AST Data classes ****************************/ + +enum class DataType { + UBYTE, + BYTE, + UWORD, + WORD, + FLOAT, + STR, + STR_S, + ARRAY_UB, + ARRAY_B, + ARRAY_UW, + ARRAY_W, + ARRAY_F; + + /** + * is the type assignable to the given other type? + */ + infix fun isAssignableTo(targetType: DataType) = + // what types are assignable to others without loss of precision? + when(this) { + UBYTE -> targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT + BYTE -> targetType == BYTE || targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT + UWORD -> targetType == UWORD || targetType == FLOAT + WORD -> targetType == WORD || targetType==UWORD || targetType == FLOAT + FLOAT -> targetType == FLOAT + STR -> targetType == STR || targetType==STR_S + STR_S -> targetType == STR || targetType==STR_S + in ArrayDatatypes -> targetType === this + else -> false + } + + + infix fun isAssignableTo(targetTypes: Set) = targetTypes.any { this isAssignableTo it } + + infix fun biggerThan(other: DataType) = + when(this) { + in ByteDatatypes -> false + in WordDatatypes -> other in ByteDatatypes + else -> true + } +} + +enum class Register { + A, + X, + Y +} + +enum class RegisterOrPair { + A, + X, + Y, + AX, + AY, + XY +} // only used in parameter and return value specs in asm subroutines + +enum class Statusflag { + Pc, + Pz, + Pv, + Pn +} + +enum class BranchCondition { + CS, + CC, + EQ, + Z, + NE, + NZ, + VS, + VC, + MI, + NEG, + PL, + POS +} + +enum class VarDeclType { + VAR, + CONST, + MEMORY +} + +val IterableDatatypes = setOf( + DataType.STR, DataType.STR_S, + DataType.ARRAY_UB, DataType.ARRAY_B, + DataType.ARRAY_UW, DataType.ARRAY_W, + DataType.ARRAY_F) +val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE) +val WordDatatypes = setOf(DataType.UWORD, DataType.WORD) +val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD) +val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT) +val StringDatatypes = setOf(DataType.STR, DataType.STR_S) +val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F) +val ArrayElementTypes = mapOf( + DataType.ARRAY_B to DataType.BYTE, + DataType.ARRAY_UB to DataType.UBYTE, + DataType.ARRAY_W to DataType.WORD, + DataType.ARRAY_UW to DataType.UWORD, + DataType.ARRAY_F to DataType.FLOAT) + +// find the parent node of a specific type or interface +// (useful to figure out in what namespace/block something is defined, etc) +inline fun findParentNode(node: Node): T? { + var candidate = node.parent + while(candidate !is T && candidate !is ParentSentinel) + candidate = candidate.parent + return if(candidate is ParentSentinel) + null + else + candidate as T +} + +object ParentSentinel : Node { + override val position = Position("<>", 0, 0, 0) + override var parent: Node = this + override fun linkParents(parent: Node) {} +} + +data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) { + override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]" +} diff --git a/compiler/src/prog8/ast/base/ErrorReporting.kt b/compiler/src/prog8/ast/base/ErrorReporting.kt new file mode 100644 index 000000000..19b4365ec --- /dev/null +++ b/compiler/src/prog8/ast/base/ErrorReporting.kt @@ -0,0 +1,37 @@ +package prog8.ast.base + +import prog8.parser.ParsingFailedError + + +fun printErrors(errors: List, moduleName: String) { + val reportedMessages = mutableSetOf() + print("\u001b[91m") // bright red + errors.forEach { + val msg = it.toString() + if(msg !in reportedMessages) { + System.err.println(msg) + reportedMessages.add(msg) + } + } + print("\u001b[0m") // reset color + if(reportedMessages.isNotEmpty()) + throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.") +} + + +fun printWarning(msg: String, position: Position, detailInfo: String?=null) { + print("\u001b[93m") // bright yellow + print("$position Warning: $msg") + if(detailInfo==null) + print("\n") + else + println(": $detailInfo\n") + print("\u001b[0m") // normal +} + + +fun printWarning(msg: String) { + print("\u001b[93m") // bright yellow + print("Warning: $msg") + print("\u001b[0m\n") // normal +} diff --git a/compiler/src/prog8/ast/base/Errors.kt b/compiler/src/prog8/ast/base/Errors.kt new file mode 100644 index 000000000..14ffe0e21 --- /dev/null +++ b/compiler/src/prog8/ast/base/Errors.kt @@ -0,0 +1,22 @@ +package prog8.ast.base + +import prog8.ast.expressions.IdentifierReference + +class FatalAstException (override var message: String) : Exception(message) + +open class AstException (override var message: String) : Exception(message) + +class SyntaxError(override var message: String, val position: Position) : AstException(message) { + override fun toString() = "$position Syntax error: $message" +} + +class NameError(override var message: String, val position: Position) : AstException(message) { + override fun toString() = "$position Name error: $message" +} + +open class ExpressionError(message: String, val position: Position) : AstException(message) { + override fun toString() = "$position Error: $message" +} + +class UndefinedSymbolError(symbol: IdentifierReference) + : ExpressionError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position) diff --git a/compiler/src/prog8/ast/base/Extensions.kt b/compiler/src/prog8/ast/base/Extensions.kt new file mode 100644 index 000000000..5eb1af007 --- /dev/null +++ b/compiler/src/prog8/ast/base/Extensions.kt @@ -0,0 +1,84 @@ +package prog8.ast.base + +import prog8.ast.* +import prog8.ast.expressions.IdentifierReference +import prog8.ast.processing.* +import prog8.ast.statements.Assignment +import prog8.ast.statements.ForLoop +import prog8.compiler.CompilationOptions + + +// the name of the subroutine that should be called for every block to initialize its variables +internal const val initvarsSubName="prog8_init_vars" + + +// prefix for literal values that are turned into a variable on the heap +internal const val autoHeapValuePrefix = "auto_heap_value_" + + +internal fun Program.checkValid(compilerOptions: CompilationOptions) { + val checker = AstChecker(this, compilerOptions) + checker.process(this) + printErrors(checker.result(), name) +} + + +internal fun Program.reorderStatements() { + val initvalueCreator = VarInitValueAndAddressOfCreator(namespace) + initvalueCreator.process(this) + + val checker = StatementReorderer(this) + checker.process(this) +} + +internal fun Module.checkImportedValid() { + val checker = ImportedAstChecker() + checker.process(this) + printErrors(checker.result(), name) +} + +internal fun Program.checkRecursion() { + val checker = AstRecursionChecker(namespace) + checker.process(this) + printErrors(checker.result(), name) +} + + +internal fun Program.checkIdentifiers() { + val checker = AstIdentifiersChecker(namespace) + checker.process(this) + + if(modules.map {it.name}.toSet().size != modules.size) { + throw FatalAstException("modules should all be unique") + } + + // add any anonymous variables for heap values that are used, + // and replace an iterable literalvalue by identifierref to new local variable + for (variable in checker.anonymousVariablesFromHeap.values) { + val scope = variable.first.definingScope() + scope.statements.add(variable.second) + val parent = variable.first.parent + when { + parent is Assignment && parent.value === variable.first -> { + val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position) + idref.linkParents(parent) + parent.value = idref + } + parent is IFunctionCall -> { + val parameterPos = parent.arglist.indexOf(variable.first) + val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position) + idref.linkParents(parent) + parent.arglist[parameterPos] = idref + } + parent is ForLoop -> { + val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position) + idref.linkParents(parent) + parent.iterable = idref + } + else -> TODO("replace literalvalue by identifierref: $variable (in $parent)") + } + variable.second.linkParents(scope as Node) + } + + printErrors(checker.result(), name) +} diff --git a/compiler/src/prog8/ast/expressions/AstExpressions.kt b/compiler/src/prog8/ast/expressions/AstExpressions.kt new file mode 100644 index 000000000..a4553b367 --- /dev/null +++ b/compiler/src/prog8/ast/expressions/AstExpressions.kt @@ -0,0 +1,793 @@ +package prog8.ast.expressions + +import prog8.ast.* +import prog8.ast.base.* +import prog8.ast.processing.IAstProcessor +import prog8.ast.statements.* +import prog8.compiler.HeapValues +import prog8.compiler.IntegerOrAddressOf +import prog8.compiler.RuntimeValue +import prog8.compiler.target.c64.Petscii +import prog8.functions.BuiltinFunctions +import prog8.functions.NotConstArgumentException +import prog8.functions.builtinFunctionReturnType +import kotlin.math.abs +import kotlin.math.floor + +class PrefixExpression(val operator: String, var expression: IExpression, override val position: Position) : IExpression { + override lateinit var parent: Node + + override fun linkParents(parent: Node) { + this.parent = parent + expression.linkParents(this) + } + + override fun constValue(program: Program): LiteralValue? = null + override fun process(processor: IAstProcessor) = processor.process(this) + override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name) + override fun inferType(program: Program): DataType? = expression.inferType(program) + + override fun toString(): String { + return "Prefix($operator $expression)" + } +} + +class BinaryExpression(var left: IExpression, var operator: String, var right: IExpression, override val position: Position) : IExpression { + override lateinit var parent: Node + + override fun linkParents(parent: Node) { + this.parent = parent + left.linkParents(this) + right.linkParents(this) + } + + override fun toString(): String { + return "[$left $operator $right]" + } + + // binary expression should actually have been optimized away into a single value, before const value was requested... + override fun constValue(program: Program): LiteralValue? = null + + override fun process(processor: IAstProcessor) = processor.process(this) + override fun referencesIdentifier(name: String) = left.referencesIdentifier(name) || right.referencesIdentifier(name) + override fun inferType(program: Program): DataType? { + val leftDt = left.inferType(program) + val rightDt = right.inferType(program) + return when (operator) { + "+", "-", "*", "**", "%" -> if (leftDt == null || rightDt == null) null else { + try { + arithmeticOpDt(leftDt, rightDt) + } catch (x: FatalAstException) { + null + } + } + "/" -> if (leftDt == null || rightDt == null) null else divisionOpDt(leftDt, rightDt) + "&" -> leftDt + "|" -> leftDt + "^" -> leftDt + "and", "or", "xor", + "<", ">", + "<=", ">=", + "==", "!=" -> DataType.UBYTE + "<<", ">>" -> leftDt + else -> throw FatalAstException("resulting datatype check for invalid operator $operator") + } + } + + companion object { + fun divisionOpDt(leftDt: DataType, rightDt: DataType): DataType { + return when (leftDt) { + DataType.UBYTE -> when (rightDt) { + DataType.UBYTE, DataType.UWORD -> DataType.UBYTE + DataType.BYTE, DataType.WORD -> DataType.WORD + DataType.FLOAT -> DataType.BYTE + else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + } + DataType.BYTE -> when (rightDt) { + in NumericDatatypes -> DataType.BYTE + else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + } + DataType.UWORD -> when (rightDt) { + DataType.UBYTE, DataType.UWORD -> DataType.UWORD + DataType.BYTE, DataType.WORD -> DataType.WORD + DataType.FLOAT -> DataType.FLOAT + else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + } + DataType.WORD -> when (rightDt) { + in NumericDatatypes -> DataType.WORD + else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + } + DataType.FLOAT -> when (rightDt) { + in NumericDatatypes -> DataType.FLOAT + else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + } + else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + } + } + + fun arithmeticOpDt(leftDt: DataType, rightDt: DataType): DataType { + return when (leftDt) { + DataType.UBYTE -> when (rightDt) { + DataType.UBYTE -> DataType.UBYTE + DataType.BYTE -> DataType.BYTE + DataType.UWORD -> DataType.UWORD + DataType.WORD -> DataType.WORD + DataType.FLOAT -> DataType.FLOAT + else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + } + DataType.BYTE -> when (rightDt) { + in ByteDatatypes -> DataType.BYTE + in WordDatatypes -> DataType.WORD + DataType.FLOAT -> DataType.FLOAT + else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + } + DataType.UWORD -> when (rightDt) { + DataType.UBYTE, DataType.UWORD -> DataType.UWORD + DataType.BYTE, DataType.WORD -> DataType.WORD + DataType.FLOAT -> DataType.FLOAT + else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + } + DataType.WORD -> when (rightDt) { + in IntegerDatatypes -> DataType.WORD + DataType.FLOAT -> DataType.FLOAT + else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + } + DataType.FLOAT -> when (rightDt) { + in NumericDatatypes -> DataType.FLOAT + else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + } + else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + } + } + } + + fun commonDatatype(leftDt: DataType, rightDt: DataType, + left: IExpression, right: IExpression): Pair { + // byte + byte -> byte + // byte + word -> word + // word + byte -> word + // word + word -> word + // a combination with a float will be float (but give a warning about this!) + + if(this.operator=="/") { + // division is a bit weird, don't cast the operands + val commondt = divisionOpDt(leftDt, rightDt) + return Pair(commondt, null) + } + + return when (leftDt) { + DataType.UBYTE -> { + when (rightDt) { + DataType.UBYTE -> Pair(DataType.UBYTE, null) + DataType.BYTE -> Pair(DataType.BYTE, left) + DataType.UWORD -> Pair(DataType.UWORD, left) + DataType.WORD -> Pair(DataType.WORD, left) + DataType.FLOAT -> Pair(DataType.FLOAT, left) + else -> throw FatalAstException("non-numeric datatype $rightDt") + } + } + DataType.BYTE -> { + when (rightDt) { + DataType.UBYTE -> Pair(DataType.BYTE, right) + DataType.BYTE -> Pair(DataType.BYTE, null) + DataType.UWORD -> Pair(DataType.WORD, left) + DataType.WORD -> Pair(DataType.WORD, left) + DataType.FLOAT -> Pair(DataType.FLOAT, left) + else -> throw FatalAstException("non-numeric datatype $rightDt") + } + } + DataType.UWORD -> { + when (rightDt) { + DataType.UBYTE -> Pair(DataType.UWORD, right) + DataType.BYTE -> Pair(DataType.UWORD, right) + DataType.UWORD -> Pair(DataType.UWORD, null) + DataType.WORD -> Pair(DataType.WORD, left) + DataType.FLOAT -> Pair(DataType.FLOAT, left) + else -> throw FatalAstException("non-numeric datatype $rightDt") + } + } + DataType.WORD -> { + when (rightDt) { + DataType.UBYTE -> Pair(DataType.WORD, right) + DataType.BYTE -> Pair(DataType.WORD, right) + DataType.UWORD -> Pair(DataType.WORD, right) + DataType.WORD -> Pair(DataType.WORD, null) + DataType.FLOAT -> Pair(DataType.FLOAT, left) + else -> throw FatalAstException("non-numeric datatype $rightDt") + } + } + DataType.FLOAT -> { + Pair(DataType.FLOAT, right) + } + else -> throw FatalAstException("non-numeric datatype $leftDt") + } + } +} + +class ArrayIndexedExpression(val identifier: IdentifierReference, + var arrayspec: ArrayIndex, + override val position: Position) : IExpression { + override lateinit var parent: Node + override fun linkParents(parent: Node) { + this.parent = parent + identifier.linkParents(this) + arrayspec.linkParents(this) + } + + override fun constValue(program: Program): LiteralValue? = null + override fun process(processor: IAstProcessor): IExpression = processor.process(this) + override fun referencesIdentifier(name: String) = identifier.referencesIdentifier(name) + + override fun inferType(program: Program): DataType? { + val target = identifier.targetStatement(program.namespace) + if (target is VarDecl) { + return when (target.datatype) { + in NumericDatatypes -> null + in StringDatatypes -> DataType.UBYTE + DataType.ARRAY_UB -> DataType.UBYTE + DataType.ARRAY_B -> DataType.BYTE + DataType.ARRAY_UW -> DataType.UWORD + DataType.ARRAY_W -> DataType.WORD + DataType.ARRAY_F -> DataType.FLOAT + else -> throw FatalAstException("invalid dt") + } + } + return null + } + + override fun toString(): String { + return "ArrayIndexed(ident=$identifier, arraysize=$arrayspec; pos=$position)" + } +} + +class TypecastExpression(var expression: IExpression, var type: DataType, val implicit: Boolean, override val position: Position) : IExpression { + override lateinit var parent: Node + + override fun linkParents(parent: Node) { + this.parent = parent + expression.linkParents(this) + } + + override fun process(processor: IAstProcessor) = processor.process(this) + override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name) + override fun inferType(program: Program): DataType? = type + override fun constValue(program: Program): LiteralValue? { + val cv = expression.constValue(program) ?: return null + val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type) + return LiteralValue.fromNumber(value.numericValue(), value.type, position) + } + + override fun toString(): String { + return "Typecast($expression as $type)" + } +} + +data class AddressOf(val identifier: IdentifierReference, override val position: Position) : IExpression { + override lateinit var parent: Node + + override fun linkParents(parent: Node) { + this.parent = parent + identifier.parent=this + } + + var scopedname: String? = null // will be set in a later state by the compiler + override fun constValue(program: Program): LiteralValue? = null + override fun referencesIdentifier(name: String) = false + override fun inferType(program: Program) = DataType.UWORD + override fun process(processor: IAstProcessor) = processor.process(this) +} + +class DirectMemoryRead(var addressExpression: IExpression, override val position: Position) : IExpression { + override lateinit var parent: Node + + override fun linkParents(parent: Node) { + this.parent = parent + this.addressExpression.linkParents(this) + } + + override fun process(processor: IAstProcessor) = processor.process(this) + override fun referencesIdentifier(name: String) = false + override fun inferType(program: Program): DataType? = DataType.UBYTE + override fun constValue(program: Program): LiteralValue? = null + + override fun toString(): String { + return "DirectMemoryRead($addressExpression)" + } +} + +class DirectMemoryWrite(var addressExpression: IExpression, override val position: Position) : IExpression { + override lateinit var parent: Node + + override fun linkParents(parent: Node) { + this.parent = parent + this.addressExpression.linkParents(this) + } + + override fun process(processor: IAstProcessor) = processor.process(this) + override fun referencesIdentifier(name: String) = false + override fun inferType(program: Program): DataType? = DataType.UBYTE + override fun constValue(program: Program): LiteralValue? = null + + override fun toString(): String { + return "DirectMemoryWrite($addressExpression)" + } +} + +open class LiteralValue(val type: DataType, + val bytevalue: Short? = null, + val wordvalue: Int? = null, + val floatvalue: Double? = null, + val strvalue: String? = null, + val arrayvalue: Array? = null, + initHeapId: Int? =null, + override val position: Position) : IExpression { + override lateinit var parent: Node + + override fun referencesIdentifier(name: String) = arrayvalue?.any { it.referencesIdentifier(name) } ?: false + + val isString = type in StringDatatypes + val isNumeric = type in NumericDatatypes + val isArray = type in ArrayDatatypes + var heapId = initHeapId + private set + + companion object { + fun fromBoolean(bool: Boolean, position: Position) = + LiteralValue(DataType.UBYTE, bytevalue = if (bool) 1 else 0, position = position) + + fun fromNumber(value: Number, type: DataType, position: Position) : LiteralValue { + return when(type) { + in ByteDatatypes -> LiteralValue(type, bytevalue = value.toShort(), position = position) + in WordDatatypes -> LiteralValue(type, wordvalue = value.toInt(), position = position) + DataType.FLOAT -> LiteralValue(type, floatvalue = value.toDouble(), position = position) + else -> throw FatalAstException("non numeric datatype") + } + } + + fun optimalNumeric(value: Number, position: Position): LiteralValue { + return if(value is Double) { + LiteralValue(DataType.FLOAT, floatvalue = value, position = position) + } else { + when (val intval = value.toInt()) { + in 0..255 -> LiteralValue(DataType.UBYTE, bytevalue = intval.toShort(), position = position) + in -128..127 -> LiteralValue(DataType.BYTE, bytevalue = intval.toShort(), position = position) + in 0..65535 -> LiteralValue(DataType.UWORD, wordvalue = intval, position = position) + in -32768..32767 -> LiteralValue(DataType.WORD, wordvalue = intval, position = position) + else -> LiteralValue(DataType.FLOAT, floatvalue = intval.toDouble(), position = position) + } + } + } + + fun optimalInteger(value: Number, position: Position): LiteralValue { + val intval = value.toInt() + if(intval.toDouble() != value.toDouble()) + throw FatalAstException("value is not an integer: $value") + return when (intval) { + in 0..255 -> LiteralValue(DataType.UBYTE, bytevalue = value.toShort(), position = position) + in -128..127 -> LiteralValue(DataType.BYTE, bytevalue = value.toShort(), position = position) + in 0..65535 -> LiteralValue(DataType.UWORD, wordvalue = value.toInt(), position = position) + else -> throw FatalAstException("integer overflow: $value") + } + } + } + + init { + when(type){ + in ByteDatatypes -> if(bytevalue==null) throw FatalAstException("literal value missing bytevalue") + in WordDatatypes -> if(wordvalue==null) throw FatalAstException("literal value missing wordvalue") + DataType.FLOAT -> if(floatvalue==null) throw FatalAstException("literal value missing floatvalue") + in StringDatatypes -> + if(strvalue==null && heapId==null) throw FatalAstException("literal value missing strvalue/heapId") + in ArrayDatatypes -> + if(arrayvalue==null && heapId==null) throw FatalAstException("literal value missing arrayvalue/heapId") + else -> throw FatalAstException("invalid type $type") + } + if(bytevalue==null && wordvalue==null && floatvalue==null && arrayvalue==null && strvalue==null && heapId==null) + throw FatalAstException("literal value without actual value") + } + + val asNumericValue: Number? = when { + bytevalue!=null -> bytevalue + wordvalue!=null -> wordvalue + floatvalue!=null -> floatvalue + else -> null + } + + val asIntegerValue: Int? = when { + bytevalue!=null -> bytevalue.toInt() + wordvalue!=null -> wordvalue + // don't round a float value, otherwise code will not detect that it's not an integer + else -> null + } + + val asBooleanValue: Boolean = + (floatvalue!=null && floatvalue != 0.0) || + (bytevalue!=null && bytevalue != 0.toShort()) || + (wordvalue!=null && wordvalue != 0) || + (strvalue!=null && strvalue.isNotEmpty()) || + (arrayvalue != null && arrayvalue.isNotEmpty()) + + override fun linkParents(parent: Node) { + this.parent = parent + arrayvalue?.forEach {it.linkParents(this)} + } + + override fun constValue(program: Program): LiteralValue? { + if(arrayvalue!=null) { + for(v in arrayvalue) { + if(v.constValue(program)==null) return null + } + } + return this + } + + override fun process(processor: IAstProcessor) = processor.process(this) + + override fun toString(): String { + val vstr = when(type) { + DataType.UBYTE -> "ubyte:$bytevalue" + DataType.BYTE -> "byte:$bytevalue" + DataType.UWORD -> "uword:$wordvalue" + DataType.WORD -> "word:$wordvalue" + DataType.FLOAT -> "float:$floatvalue" + in StringDatatypes -> "str:$strvalue" + in ArrayDatatypes -> "array:$arrayvalue" + else -> throw FatalAstException("weird datatype") + } + return "LiteralValue($vstr)" + } + + override fun inferType(program: Program) = type + + override fun hashCode(): Int { + val bh = bytevalue?.hashCode() ?: 0x10001234 + val wh = wordvalue?.hashCode() ?: 0x01002345 + val fh = floatvalue?.hashCode() ?: 0x00103456 + val sh = strvalue?.hashCode() ?: 0x00014567 + val ah = arrayvalue?.hashCode() ?: 0x11119876 + var hash = bh * 31 xor wh + hash = hash*31 xor fh + hash = hash*31 xor sh + hash = hash*31 xor ah + hash = hash*31 xor type.hashCode() + return hash + } + + override fun equals(other: Any?): Boolean { + if(other==null || other !is LiteralValue) + return false + if(isNumeric && other.isNumeric) + return asNumericValue?.toDouble()==other.asNumericValue?.toDouble() + if(isArray && other.isArray) + return arrayvalue!!.contentEquals(other.arrayvalue!!) && heapId==other.heapId + if(isString && other.isString) + return strvalue==other.strvalue && heapId==other.heapId + + if(type!=other.type) + return false + + return compareTo(other) == 0 + } + + operator fun compareTo(other: LiteralValue): Int { + val numLeft = asNumericValue?.toDouble() + val numRight = other.asNumericValue?.toDouble() + if(numLeft!=null && numRight!=null) + return numLeft.compareTo(numRight) + + if(strvalue!=null && other.strvalue!=null) + return strvalue.compareTo(other.strvalue) + + throw ExpressionError("cannot order compare type $type with ${other.type}", other.position) + } + + fun intoDatatype(targettype: DataType): LiteralValue? { + if(type==targettype) + return this + when(type) { + DataType.UBYTE -> { + if(targettype== DataType.BYTE && bytevalue!! <= 127) + return LiteralValue(targettype, bytevalue = bytevalue, position = position) + if(targettype== DataType.WORD || targettype== DataType.UWORD) + return LiteralValue(targettype, wordvalue = bytevalue!!.toInt(), position = position) + if(targettype== DataType.FLOAT) + return LiteralValue(targettype, floatvalue = bytevalue!!.toDouble(), position = position) + } + DataType.BYTE -> { + if(targettype== DataType.UBYTE && bytevalue!! >= 0) + return LiteralValue(targettype, bytevalue = bytevalue, position = position) + if(targettype== DataType.UWORD && bytevalue!! >= 0) + return LiteralValue(targettype, wordvalue = bytevalue.toInt(), position = position) + if(targettype== DataType.WORD) + return LiteralValue(targettype, wordvalue = bytevalue!!.toInt(), position = position) + if(targettype== DataType.FLOAT) + return LiteralValue(targettype, floatvalue = bytevalue!!.toDouble(), position = position) + } + DataType.UWORD -> { + if(targettype== DataType.BYTE && wordvalue!! <= 127) + return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position) + if(targettype== DataType.UBYTE && wordvalue!! <= 255) + return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position) + if(targettype== DataType.WORD && wordvalue!! <= 32767) + return LiteralValue(targettype, wordvalue = wordvalue, position = position) + if(targettype== DataType.FLOAT) + return LiteralValue(targettype, floatvalue = wordvalue!!.toDouble(), position = position) + } + DataType.WORD -> { + if(targettype== DataType.BYTE && wordvalue!! in -128..127) + return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position) + if(targettype== DataType.UBYTE && wordvalue!! in 0..255) + return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position) + if(targettype== DataType.UWORD && wordvalue!! >=0) + return LiteralValue(targettype, wordvalue = wordvalue, position = position) + if(targettype== DataType.FLOAT) + return LiteralValue(targettype, floatvalue = wordvalue!!.toDouble(), position = position) + } + DataType.FLOAT -> { + if(floor(floatvalue!!) ==floatvalue) { + val value = floatvalue.toInt() + if (targettype == DataType.BYTE && value in -128..127) + return LiteralValue(targettype, bytevalue = value.toShort(), position = position) + if (targettype == DataType.UBYTE && value in 0..255) + return LiteralValue(targettype, bytevalue = value.toShort(), position = position) + if (targettype == DataType.WORD && value in -32768..32767) + return LiteralValue(targettype, wordvalue = value, position = position) + if (targettype == DataType.UWORD && value in 0..65535) + return LiteralValue(targettype, wordvalue = value, position = position) + } + } + in StringDatatypes -> { + if(targettype in StringDatatypes) + return this + } + else -> {} + } + return null // invalid type conversion from $this to $targettype + } + + fun addToHeap(heap: HeapValues) { + if(heapId==null) { + if (strvalue != null) { + heapId = heap.addString(type, strvalue) + } + else if (arrayvalue!=null) { + if(arrayvalue.any {it is AddressOf }) { + val intArrayWithAddressOfs = arrayvalue.map { + when (it) { + is AddressOf -> IntegerOrAddressOf(null, it) + is LiteralValue -> IntegerOrAddressOf(it.asIntegerValue, null) + else -> throw FatalAstException("invalid datatype in array") + } + } + heapId = heap.addIntegerArray(type, intArrayWithAddressOfs.toTypedArray()) + } else { + val valuesInArray = arrayvalue.map { (it as LiteralValue).asNumericValue!! } + heapId = if(type== DataType.ARRAY_F) { + val doubleArray = valuesInArray.map { it.toDouble() }.toDoubleArray() + heap.addDoublesArray(doubleArray) + } else { + val integerArray = valuesInArray.map { it.toInt() } + heap.addIntegerArray(type, integerArray.map { IntegerOrAddressOf(it, null) }.toTypedArray()) + } + } + } + } + } +} + +class RangeExpr(var from: IExpression, + var to: IExpression, + var step: IExpression, + override val position: Position) : IExpression { + override lateinit var parent: Node + + override fun linkParents(parent: Node) { + this.parent = parent + from.linkParents(this) + to.linkParents(this) + step.linkParents(this) + } + + override fun constValue(program: Program): LiteralValue? = null + override fun process(processor: IAstProcessor) = processor.process(this) + override fun referencesIdentifier(name: String): Boolean = from.referencesIdentifier(name) || to.referencesIdentifier(name) + override fun inferType(program: Program): DataType? { + val fromDt=from.inferType(program) + val toDt=to.inferType(program) + return when { + fromDt==null || toDt==null -> null + fromDt== DataType.UBYTE && toDt== DataType.UBYTE -> DataType.UBYTE + fromDt== DataType.UWORD && toDt== DataType.UWORD -> DataType.UWORD + fromDt== DataType.STR && toDt== DataType.STR -> DataType.STR + fromDt== DataType.STR_S && toDt== DataType.STR_S -> DataType.STR_S + fromDt== DataType.WORD || toDt== DataType.WORD -> DataType.WORD + fromDt== DataType.BYTE || toDt== DataType.BYTE -> DataType.BYTE + else -> DataType.UBYTE + } + } + override fun toString(): String { + return "RangeExpr(from $from, to $to, step $step, pos=$position)" + } + + fun size(): Int? { + val fromLv = (from as? LiteralValue) + val toLv = (to as? LiteralValue) + if(fromLv==null || toLv==null) + return null + return toConstantIntegerRange()?.count() + } + + fun toConstantIntegerRange(): IntProgression? { + val fromLv = from as? LiteralValue + val toLv = to as? LiteralValue + if(fromLv==null || toLv==null) + return null // non-constant range + val fromVal: Int + val toVal: Int + if(fromLv.isString && toLv.isString) { + // string range -> int range over petscii values + fromVal = Petscii.encodePetscii(fromLv.strvalue!!, true)[0].toInt() + toVal = Petscii.encodePetscii(toLv.strvalue!!, true)[0].toInt() + } else { + // integer range + fromVal = (from as LiteralValue).asIntegerValue!! + toVal = (to as LiteralValue).asIntegerValue!! + } + val stepVal = (step as? LiteralValue)?.asIntegerValue ?: 1 + return when { + fromVal <= toVal -> when { + stepVal <= 0 -> IntRange.EMPTY + stepVal == 1 -> fromVal..toVal + else -> fromVal..toVal step stepVal + } + else -> when { + stepVal >= 0 -> IntRange.EMPTY + stepVal == -1 -> fromVal downTo toVal + else -> fromVal downTo toVal step abs(stepVal) + } + } + } +} + +class RegisterExpr(val register: Register, override val position: Position) : IExpression { + override lateinit var parent: Node + + override fun linkParents(parent: Node) { + this.parent = parent + } + + override fun constValue(program: Program): LiteralValue? = null + override fun process(processor: IAstProcessor) = this + override fun referencesIdentifier(name: String): Boolean = false + override fun toString(): String { + return "RegisterExpr(register=$register, pos=$position)" + } + + override fun inferType(program: Program) = DataType.UBYTE +} + +data class IdentifierReference(val nameInSource: List, override val position: Position) : IExpression { + override lateinit var parent: Node + + fun targetStatement(namespace: INameScope) = + if(nameInSource.size==1 && nameInSource[0] in BuiltinFunctions) + BuiltinFunctionStatementPlaceholder(nameInSource[0], position) + else + namespace.lookup(nameInSource, this) + + fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl + fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine + + override fun linkParents(parent: Node) { + this.parent = parent + } + + override fun constValue(program: Program): LiteralValue? { + val node = program.namespace.lookup(nameInSource, this) + ?: throw UndefinedSymbolError(this) + val vardecl = node as? VarDecl + if(vardecl==null) { + return null + } else if(vardecl.type!= VarDeclType.CONST) { + return null + } + return vardecl.value?.constValue(program) + } + + override fun toString(): String { + return "IdentifierRef($nameInSource)" + } + + override fun process(processor: IAstProcessor) = processor.process(this) + override fun referencesIdentifier(name: String): Boolean = nameInSource.last() == name // @todo is this correct all the time? + + override fun inferType(program: Program): DataType? { + val targetStmt = targetStatement(program.namespace) + if(targetStmt is VarDecl) { + return targetStmt.datatype + } else { + throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position") + } + } + + fun heapId(namespace: INameScope): Int { + val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolError(this) + return ((node as? VarDecl)?.value as? LiteralValue)?.heapId ?: throw FatalAstException("identifier is not on the heap: $this") + } +} + +class FunctionCall(override var target: IdentifierReference, + override var arglist: MutableList, + override val position: Position) : IExpression, IFunctionCall { + override lateinit var parent: Node + + override fun linkParents(parent: Node) { + this.parent = parent + target.linkParents(this) + arglist.forEach { it.linkParents(this) } + } + + override fun constValue(program: Program) = constValue(program, true) + + private fun constValue(program: Program, withDatatypeCheck: Boolean): LiteralValue? { + // if the function is a built-in function and the args are consts, should try to const-evaluate! + // lenghts of arrays and strings are constants that are determined at compile time! + if(target.nameInSource.size>1) return null + try { + var resultValue: LiteralValue? = null + val func = BuiltinFunctions[target.nameInSource[0]] + if(func!=null) { + val exprfunc = func.constExpressionFunc + if(exprfunc!=null) + resultValue = exprfunc(arglist, position, program) + else if(func.returntype==null) + throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used here because it doesn't return a value", position) + } + + if(withDatatypeCheck) { + val resultDt = this.inferType(program) + if(resultValue==null || resultDt == resultValue.type) + return resultValue + throw FatalAstException("evaluated const expression result value doesn't match expected datatype $resultDt, pos=$position") + } else { + return resultValue + } + } + catch(x: NotConstArgumentException) { + // const-evaluating the builtin function call failed. + return null + } + } + + override fun toString(): String { + return "FunctionCall(target=$target, pos=$position)" + } + + override fun process(processor: IAstProcessor) = processor.process(this) + override fun referencesIdentifier(name: String): Boolean = target.referencesIdentifier(name) || arglist.any{it.referencesIdentifier(name)} + + override fun inferType(program: Program): DataType? { + val constVal = constValue(program ,false) + if(constVal!=null) + return constVal.type + val stmt = target.targetStatement(program.namespace) ?: return null + when (stmt) { + is BuiltinFunctionStatementPlaceholder -> { + if(target.nameInSource[0] == "set_carry" || target.nameInSource[0]=="set_irqd" || + target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") { + return null // these have no return value + } + return builtinFunctionReturnType(target.nameInSource[0], this.arglist, program) + } + is Subroutine -> { + if(stmt.returntypes.isEmpty()) + return null // no return value + if(stmt.returntypes.size==1) + return stmt.returntypes[0] + return null // has multiple return types... so not a single resulting datatype possible + } + is Label -> return null + } + return null // calling something we don't recognise... + } +} diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/processing/AstChecker.kt similarity index 93% rename from compiler/src/prog8/ast/AstChecker.kt rename to compiler/src/prog8/ast/processing/AstChecker.kt index 293739e10..a354b48fa 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/processing/AstChecker.kt @@ -1,58 +1,19 @@ -package prog8.ast +package prog8.ast.processing +import prog8.ast.* +import prog8.ast.base.* +import prog8.ast.base.printWarning +import prog8.ast.expressions.* +import prog8.ast.statements.* import prog8.compiler.CompilationOptions import prog8.compiler.HeapValues import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE import prog8.functions.BuiltinFunctions -import prog8.parser.ParsingFailedError import java.io.File -/** - * General checks on the Ast - */ - -internal fun Program.checkValid(compilerOptions: CompilationOptions) { - val checker = AstChecker(this, compilerOptions) - checker.process(this) - printErrors(checker.result(), name) -} - - -fun printErrors(errors: List, moduleName: String) { - val reportedMessages = mutableSetOf() - print("\u001b[91m") // bright red - errors.forEach { - val msg = it.toString() - if(msg !in reportedMessages) { - System.err.println(msg) - reportedMessages.add(msg) - } - } - print("\u001b[0m") // reset color - if(reportedMessages.isNotEmpty()) - throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.") -} - - -fun printWarning(msg: String, position: Position, detailInfo: String?=null) { - print("\u001b[93m") // bright yellow - print("$position Warning: $msg") - if(detailInfo==null) - print("\n") - else - println(": $detailInfo\n") - print("\u001b[0m") // normal -} - -fun printWarning(msg: String) { - print("\u001b[93m") // bright yellow - print("Warning: $msg") - print("\u001b[0m\n") // normal -} - -private class AstChecker(private val program: Program, - private val compilerOptions: CompilationOptions) : IAstProcessor { +internal class AstChecker(private val program: Program, + private val compilerOptions: CompilationOptions) : IAstProcessor { private val checkResult: MutableList = mutableListOf() private val heapStringSentinel: Int init { @@ -142,7 +103,7 @@ private class AstChecker(private val program: Program, for (rv in expectedReturnValues.withIndex().zip(returnStmt.values)) { val valueDt=rv.second.inferType(program) if(rv.first.value!=valueDt) - checkResult.add(ExpressionError("type $valueDt of return value #${rv.first.index+1} doesn't match subroutine return type ${rv.first.value}", rv.second.position)) + checkResult.add(ExpressionError("type $valueDt of return value #${rv.first.index + 1} doesn't match subroutine return type ${rv.first.value}", rv.second.position)) } return super.process(returnStmt) } @@ -158,36 +119,36 @@ private class AstChecker(private val program: Program, if (forLoop.loopRegister != null) { printWarning("using a register as loop variable is risky (it could get clobbered in the body)", forLoop.position) // loop register - if (iterableDt != DataType.UBYTE && iterableDt!=DataType.ARRAY_UB && iterableDt !in StringDatatypes) + if (iterableDt != DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt !in StringDatatypes) checkResult.add(ExpressionError("register can only loop over bytes", forLoop.position)) } else { // loop variable val loopvar = forLoop.loopVar!!.targetVarDecl(program.namespace) - if(loopvar==null || loopvar.type==VarDeclType.CONST) { + if(loopvar==null || loopvar.type== VarDeclType.CONST) { checkResult.add(SyntaxError("for loop requires a variable to loop with", forLoop.position)) } else { when (loopvar.datatype) { DataType.UBYTE -> { - if(iterableDt!=DataType.UBYTE && iterableDt!=DataType.ARRAY_UB && iterableDt !in StringDatatypes) + if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt !in StringDatatypes) checkResult.add(ExpressionError("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position)) } DataType.UWORD -> { - if(iterableDt!=DataType.UBYTE && iterableDt!=DataType.UWORD && iterableDt !in StringDatatypes && - iterableDt !=DataType.ARRAY_UB && iterableDt!=DataType.ARRAY_UW) + if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt !in StringDatatypes && + iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW) checkResult.add(ExpressionError("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position)) } DataType.BYTE -> { - if(iterableDt!=DataType.BYTE && iterableDt!=DataType.ARRAY_B) + if(iterableDt!= DataType.BYTE && iterableDt!= DataType.ARRAY_B) checkResult.add(ExpressionError("byte loop variable can only loop over bytes", forLoop.position)) } DataType.WORD -> { - if(iterableDt!=DataType.BYTE && iterableDt!=DataType.WORD && - iterableDt !=DataType.ARRAY_B && iterableDt!=DataType.ARRAY_W) + if(iterableDt!= DataType.BYTE && iterableDt!= DataType.WORD && + iterableDt != DataType.ARRAY_B && iterableDt!= DataType.ARRAY_W) checkResult.add(ExpressionError("word loop variable can only loop over bytes or words", forLoop.position)) } DataType.FLOAT -> { - if(iterableDt!=DataType.FLOAT && iterableDt != DataType.ARRAY_F) + if(iterableDt!= DataType.FLOAT && iterableDt != DataType.ARRAY_F) checkResult.add(ExpressionError("float loop variable can only loop over floats", forLoop.position)) } else -> checkResult.add(ExpressionError("loop variable must be numeric type", forLoop.position)) @@ -351,7 +312,7 @@ private class AstChecker(private val program: Program, // This is not easy to fix because strings and arrays are treated a bit simplistic (a "virtual" pointer to the value on the heap) // while passing them as subroutine parameters would require a "real" pointer OR copying the VALUE to the subroutine's parameter variable (which is very inefficient). // For now, don't pass strings and arrays as parameters and instead create the workaround as suggested in the error message below. - if(!subroutine.parameters.all{it.type in NumericDatatypes}) { + if(!subroutine.parameters.all{it.type in NumericDatatypes }) { err("Non-asm subroutine can only take numerical parameters (no str/array types) for now. Workaround (for nested subroutine): access the variable from the outer scope directly.") } } @@ -494,19 +455,19 @@ private class AstChecker(private val program: Program, } // CONST can only occur on simple types (byte, word, float) - if(decl.type==VarDeclType.CONST) { + if(decl.type== VarDeclType.CONST) { if (decl.datatype !in NumericDatatypes) err("const modifier can only be used on numeric types (byte, word, float)") } // FLOATS - if(!compilerOptions.floats && decl.datatype in setOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!=VarDeclType.MEMORY) { + if(!compilerOptions.floats && decl.datatype in setOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY) { checkResult.add(SyntaxError("floating point used, but that is not enabled via options", decl.position)) } // ARRAY without size specifier MUST have an iterable initializer value if(decl.isArray && decl.arraysize==null) { - if(decl.type==VarDeclType.MEMORY) + if(decl.type== VarDeclType.MEMORY) checkResult.add(SyntaxError("memory mapped array must have a size specification", decl.position)) if(decl.value==null) { checkResult.add(SyntaxError("array variable is missing a size specification or an initialization value", decl.position)) @@ -528,15 +489,15 @@ private class AstChecker(private val program: Program, // initialize numeric var with value zero by default. val litVal = when { - decl.datatype in ByteDatatypes -> LiteralValue(decl.datatype, bytevalue=0, position = decl.position) - decl.datatype in WordDatatypes -> LiteralValue(decl.datatype, wordvalue=0, position = decl.position) - else -> LiteralValue(decl.datatype, floatvalue=0.0, position = decl.position) + decl.datatype in ByteDatatypes -> LiteralValue(decl.datatype, bytevalue = 0, position = decl.position) + decl.datatype in WordDatatypes -> LiteralValue(decl.datatype, wordvalue = 0, position = decl.position) + else -> LiteralValue(decl.datatype, floatvalue = 0.0, position = decl.position) } litVal.parent = decl decl.value = litVal } - decl.type==VarDeclType.VAR -> { - val litVal = LiteralValue(decl.datatype, initHeapId = heapStringSentinel, position=decl.position) // point to the sentinel heap value instead + decl.type== VarDeclType.VAR -> { + val litVal = LiteralValue(decl.datatype, initHeapId = heapStringSentinel, position = decl.position) // point to the sentinel heap value instead litVal.parent=decl decl.value = litVal } @@ -728,7 +689,7 @@ private class AstChecker(private val program: Program, if(divisor==0.0) checkResult.add(ExpressionError("division by zero", expr.right.position)) if(expr.operator=="%") { - if ((rightDt != DataType.UBYTE && rightDt != DataType.UWORD) || (leftDt!=DataType.UBYTE && leftDt!=DataType.UWORD)) + if ((rightDt != DataType.UBYTE && rightDt != DataType.UWORD) || (leftDt!= DataType.UBYTE && leftDt!= DataType.UWORD)) checkResult.add(ExpressionError("remainder can only be used on unsigned integer operands", expr.right.position)) } } @@ -864,7 +825,7 @@ private class AstChecker(private val program: Program, val argDt = arg.first.value.inferType(program) if(argDt!=null && !(argDt isAssignableTo arg.second.type)) { // for asm subroutines having STR param it's okay to provide a UWORD too (pointer value) - if(!(target.isAsmSubroutine && arg.second.type in StringDatatypes && argDt==DataType.UWORD)) + if(!(target.isAsmSubroutine && arg.second.type in StringDatatypes && argDt== DataType.UWORD)) checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position)) } @@ -878,13 +839,13 @@ private class AstChecker(private val program: Program, val asmParamReg = target.asmParameterRegisters[arg.first.index] if(asmParamReg.statusflag!=null) { if(argDt !in ByteDatatypes) - checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index+1} must be byte type for statusflag", position)) + checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for statusflag", position)) } else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) { if(argDt !in ByteDatatypes) - checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index+1} must be byte type for single register", position)) + checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for single register", position)) } else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) { - if(argDt !in WordDatatypes+ IterableDatatypes) - checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index+1} must be word type for register pair", position)) + if(argDt !in WordDatatypes + IterableDatatypes) + checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} must be word type for register pair", position)) } } } @@ -899,7 +860,7 @@ private class AstChecker(private val program: Program, if(target==null) { checkResult.add(SyntaxError("undefined symbol: ${targetName.joinToString(".")}", postIncrDecr.position)) } else { - if(target !is VarDecl || target.type==VarDeclType.CONST) { + if(target !is VarDecl || target.type== VarDeclType.CONST) { checkResult.add(SyntaxError("can only increment or decrement a variable", postIncrDecr.position)) } else if(target.datatype !in NumericDatatypes) { checkResult.add(SyntaxError("can only increment or decrement a byte/float/word variable", postIncrDecr.position)) @@ -945,7 +906,7 @@ private class AstChecker(private val program: Program, // check index value 0..255 val dtx = arrayIndexedExpression.arrayspec.index.inferType(program) - if(dtx!=DataType.UBYTE && dtx!=DataType.BYTE) + if(dtx!= DataType.UBYTE && dtx!= DataType.BYTE) checkResult.add(SyntaxError("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)) return super.process(arrayIndexedExpression) @@ -1121,7 +1082,7 @@ private class AstChecker(private val program: Program, value.arrayvalue.map {it.constValue(program)?.asNumericValue!!.toDouble()}.toDoubleArray() else heap.get(value.heapId!!).doubleArray!! - if(doubles.any { it < FLOAT_MAX_NEGATIVE || it> FLOAT_MAX_POSITIVE}) + if(doubles.any { it < FLOAT_MAX_NEGATIVE || it> FLOAT_MAX_POSITIVE }) return err("floating point value overflow") return true } @@ -1190,26 +1151,26 @@ private class AstChecker(private val program: Program, checkResult.add(SyntaxError("can't assign a range value", position)) val result = when(targetDatatype) { - DataType.BYTE -> sourceDatatype==DataType.BYTE - DataType.UBYTE -> sourceDatatype==DataType.UBYTE - DataType.WORD -> sourceDatatype==DataType.BYTE || sourceDatatype==DataType.UBYTE || sourceDatatype==DataType.WORD - DataType.UWORD -> sourceDatatype==DataType.UBYTE || sourceDatatype==DataType.UWORD + DataType.BYTE -> sourceDatatype== DataType.BYTE + DataType.UBYTE -> sourceDatatype== DataType.UBYTE + DataType.WORD -> sourceDatatype== DataType.BYTE || sourceDatatype== DataType.UBYTE || sourceDatatype== DataType.WORD + DataType.UWORD -> sourceDatatype== DataType.UBYTE || sourceDatatype== DataType.UWORD DataType.FLOAT -> sourceDatatype in NumericDatatypes - DataType.STR -> sourceDatatype==DataType.STR - DataType.STR_S -> sourceDatatype==DataType.STR_S + DataType.STR -> sourceDatatype== DataType.STR + DataType.STR_S -> sourceDatatype== DataType.STR_S else -> checkResult.add(SyntaxError("cannot assign new value to variable of type $targetDatatype", position)) } if(result) return true - if((sourceDatatype==DataType.UWORD || sourceDatatype==DataType.WORD) && (targetDatatype==DataType.UBYTE || targetDatatype==DataType.BYTE)) { + if((sourceDatatype== DataType.UWORD || sourceDatatype== DataType.WORD) && (targetDatatype== DataType.UBYTE || targetDatatype== DataType.BYTE)) { if(assignTargets.size==2 && assignTargets[0].register!=null && assignTargets[1].register!=null) return true // for asm subroutine calls that return a (U)WORD that's going to be stored into two BYTES (registers), we make an exception. else checkResult.add(ExpressionError("cannot assign word to byte, use msb() or lsb()?", position)) } - else if(sourceDatatype==DataType.FLOAT && targetDatatype in IntegerDatatypes) + else if(sourceDatatype== DataType.FLOAT && targetDatatype in IntegerDatatypes) checkResult.add(ExpressionError("cannot assign float to ${targetDatatype.name.toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)) else checkResult.add(ExpressionError("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position)) diff --git a/compiler/src/prog8/ast/AstIdentifiersChecker.kt b/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt similarity index 82% rename from compiler/src/prog8/ast/AstIdentifiersChecker.kt rename to compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt index 1c6e6a228..183cb8ae2 100644 --- a/compiler/src/prog8/ast/AstIdentifiersChecker.kt +++ b/compiler/src/prog8/ast/processing/AstIdentifiersChecker.kt @@ -1,54 +1,14 @@ -package prog8.ast +package prog8.ast.processing +import prog8.ast.* +import prog8.ast.base.* +import prog8.ast.base.autoHeapValuePrefix +import prog8.ast.expressions.* +import prog8.ast.statements.* import prog8.functions.BuiltinFunctions -/** - * Checks the validity of all identifiers (no conflicts) - * Also makes sure that subroutine's parameters also become local variable decls in the subroutine's scope. - * Finally, it also makes sure the datatype of all Var decls and sub Return values is set correctly. - */ -internal fun Program.checkIdentifiers() { - val checker = AstIdentifiersChecker(namespace) - checker.process(this) - - if(modules.map {it.name}.toSet().size != modules.size) { - throw FatalAstException("modules should all be unique") - } - - // add any anonymous variables for heap values that are used, - // and replace an iterable literalvalue by identifierref to new local variable - for (variable in checker.anonymousVariablesFromHeap.values) { - val scope = variable.first.definingScope() - scope.statements.add(variable.second) - val parent = variable.first.parent - when { - parent is Assignment && parent.value === variable.first -> { - val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position) - idref.linkParents(parent) - parent.value = idref - } - parent is IFunctionCall -> { - val parameterPos = parent.arglist.indexOf(variable.first) - val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position) - idref.linkParents(parent) - parent.arglist[parameterPos] = idref - } - parent is ForLoop -> { - val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position) - idref.linkParents(parent) - parent.iterable = idref - } - else -> TODO("replace literalvalue by identifierref: $variable (in $parent)") - } - variable.second.linkParents(scope as Node) - } - - printErrors(checker.result(), name) -} - - -private class AstIdentifiersChecker(private val namespace: INameScope) : IAstProcessor { +internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstProcessor { private val checkResult: MutableList = mutableListOf() private var blocks: MutableMap = mutableMapOf() @@ -202,7 +162,7 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro } override fun process(assignTarget: AssignTarget): AssignTarget { - if(assignTarget.register==Register.X) + if(assignTarget.register== Register.X) printWarning("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position) return super.process(assignTarget) } @@ -254,5 +214,3 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro } } - -internal const val autoHeapValuePrefix = "auto_heap_value_" diff --git a/compiler/src/prog8/ast/processing/AstRecursionChecker.kt b/compiler/src/prog8/ast/processing/AstRecursionChecker.kt new file mode 100644 index 000000000..e2cb75ebf --- /dev/null +++ b/compiler/src/prog8/ast/processing/AstRecursionChecker.kt @@ -0,0 +1,117 @@ +package prog8.ast.processing + +import prog8.ast.* +import prog8.ast.base.AstException +import prog8.ast.expressions.FunctionCall +import prog8.ast.statements.FunctionCallStatement +import prog8.ast.statements.Subroutine + + +internal class AstRecursionChecker(private val namespace: INameScope) : IAstProcessor { + private val callGraph = DirectedGraph() + + internal fun result(): List { + val cycle = callGraph.checkForCycle() + if(cycle.isEmpty()) + return emptyList() + val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" } + return listOf(AstException("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain")) + } + + override fun process(functionCallStatement: FunctionCallStatement): IStatement { + val scope = functionCallStatement.definingScope() + val targetStatement = functionCallStatement.target.targetStatement(namespace) + if(targetStatement!=null) { + val targetScope = when (targetStatement) { + is Subroutine -> targetStatement + else -> targetStatement.definingScope() + } + callGraph.add(scope, targetScope) + } + return super.process(functionCallStatement) + } + + override fun process(functionCall: FunctionCall): IExpression { + val scope = functionCall.definingScope() + val targetStatement = functionCall.target.targetStatement(namespace) + if(targetStatement!=null) { + val targetScope = when (targetStatement) { + is Subroutine -> targetStatement + else -> targetStatement.definingScope() + } + callGraph.add(scope, targetScope) + } + return super.process(functionCall) + } + + + private class DirectedGraph { + private val graph = mutableMapOf>() + private var uniqueVertices = mutableSetOf() + val numVertices : Int + get() = uniqueVertices.size + + fun add(from: VT, to: VT) { + var targets = graph[from] + if(targets==null) { + targets = mutableSetOf() + graph[from] = targets + } + targets.add(to) + uniqueVertices.add(from) + uniqueVertices.add(to) + } + + fun print() { + println("#vertices: $numVertices") + graph.forEach { (from, to) -> + println("$from CALLS:") + to.forEach { println(" $it") } + } + val cycle = checkForCycle() + if(cycle.isNotEmpty()) { + println("CYCLIC! $cycle") + } + } + + fun checkForCycle(): MutableList { + val visited = uniqueVertices.associateWith { false }.toMutableMap() + val recStack = uniqueVertices.associateWith { false }.toMutableMap() + val cycle = mutableListOf() + for(node in uniqueVertices) { + if(isCyclicUntil(node, visited, recStack, cycle)) + return cycle + } + return mutableListOf() + } + + private fun isCyclicUntil(node: VT, + visited: MutableMap, + recStack: MutableMap, + cycleNodes: MutableList): Boolean { + + if(recStack[node]==true) return true + if(visited[node]==true) return false + + // mark current node as visited and add to recursion stack + visited[node] = true + recStack[node] = true + + // recurse for all neighbours + val neighbors = graph[node] + if(neighbors!=null) { + for (neighbour in neighbors) { + if (isCyclicUntil(neighbour, visited, recStack, cycleNodes)) { + cycleNodes.add(node) + return true + } + } + } + + // pop node from recursion stack + recStack[node] = false + return false + } + } + +} diff --git a/compiler/src/prog8/ast/processing/IAstProcessor.kt b/compiler/src/prog8/ast/processing/IAstProcessor.kt new file mode 100644 index 000000000..566ed40db --- /dev/null +++ b/compiler/src/prog8/ast/processing/IAstProcessor.kt @@ -0,0 +1,197 @@ +package prog8.ast.processing + +import prog8.ast.* +import prog8.ast.expressions.* +import prog8.ast.statements.* + +interface IAstProcessor { + fun process(program: Program) { + program.modules.forEach { process(it) } + } + + fun process(module: Module) { + module.statements = module.statements.asSequence().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.asSequence().map { it.process(this) }.toMutableList() + return block + } + + fun process(decl: VarDecl): IStatement { + decl.value = decl.value?.process(this) + decl.arraysize?.process(this) + return decl + } + + fun process(subroutine: Subroutine): IStatement { + subroutine.statements = subroutine.statements.asSequence().map { it.process(this) }.toMutableList() + return subroutine + } + + fun process(functionCall: FunctionCall): IExpression { + val newtarget = functionCall.target.process(this) + if(newtarget is IdentifierReference) + functionCall.target = newtarget + functionCall.arglist = functionCall.arglist.map { it.process(this) }.toMutableList() + return functionCall + } + + fun process(functionCallStatement: FunctionCallStatement): IStatement { + val newtarget = functionCallStatement.target.process(this) + if(newtarget is IdentifierReference) + functionCallStatement.target = newtarget + functionCallStatement.arglist = functionCallStatement.arglist.map { it.process(this) }.toMutableList() + return functionCallStatement + } + + 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 { + if(jump.identifier!=null) { + val ident = jump.identifier.process(this) + if(ident is IdentifierReference && ident!==jump.identifier) { + return Jump(null, ident, null, jump.position) + } + } + return jump + } + + fun process(ifStatement: IfStatement): IStatement { + ifStatement.condition = ifStatement.condition.process(this) + ifStatement.truepart = ifStatement.truepart.process(this) as AnonymousScope + ifStatement.elsepart = ifStatement.elsepart.process(this) as AnonymousScope + return ifStatement + } + + fun process(branchStatement: BranchStatement): IStatement { + branchStatement.truepart = branchStatement.truepart.process(this) as AnonymousScope + branchStatement.elsepart = branchStatement.elsepart.process(this) as AnonymousScope + return branchStatement + } + + fun process(range: RangeExpr): IExpression { + range.from = range.from.process(this) + range.to = range.to.process(this) + range.step = range.step.process(this) + return range + } + + fun process(label: Label): IStatement { + return label + } + + fun process(literalValue: LiteralValue): LiteralValue { + if(literalValue.arrayvalue!=null) { + for(av in literalValue.arrayvalue.withIndex()) { + val newvalue = av.value.process(this) + literalValue.arrayvalue[av.index] = newvalue + } + } + return literalValue + } + + fun process(assignment: Assignment): IStatement { + assignment.targets = assignment.targets.map { it.process(this) } + assignment.value = assignment.value.process(this) + return assignment + } + + fun process(postIncrDecr: PostIncrDecr): IStatement { + postIncrDecr.target = postIncrDecr.target.process(this) + return postIncrDecr + } + + fun process(contStmt: Continue): IStatement { + return contStmt + } + + fun process(breakStmt: Break): IStatement { + return breakStmt + } + + fun process(forLoop: ForLoop): IStatement { + forLoop.loopVar?.process(this) + forLoop.iterable = forLoop.iterable.process(this) + forLoop.body = forLoop.body.process(this) as AnonymousScope + return forLoop + } + + fun process(whileLoop: WhileLoop): IStatement { + whileLoop.condition = whileLoop.condition.process(this) + whileLoop.body = whileLoop.body.process(this) as AnonymousScope + return whileLoop + } + + fun process(repeatLoop: RepeatLoop): IStatement { + repeatLoop.untilCondition = repeatLoop.untilCondition.process(this) + repeatLoop.body = repeatLoop.body.process(this) as AnonymousScope + return repeatLoop + } + + fun process(returnStmt: Return): IStatement { + returnStmt.values = returnStmt.values.map { it.process(this) } + return returnStmt + } + + fun process(arrayIndexedExpression: ArrayIndexedExpression): IExpression { + arrayIndexedExpression.identifier.process(this) + arrayIndexedExpression.arrayspec.process(this) + return arrayIndexedExpression + } + + fun process(assignTarget: AssignTarget): AssignTarget { + assignTarget.arrayindexed?.process(this) + assignTarget.identifier?.process(this) + assignTarget.memoryAddress?.process(this) + return assignTarget + } + + fun process(scope: AnonymousScope): IStatement { + scope.statements = scope.statements.asSequence().map { it.process(this) }.toMutableList() + return scope + } + + fun process(typecast: TypecastExpression): IExpression { + typecast.expression = typecast.expression.process(this) + return typecast + } + + fun process(memread: DirectMemoryRead): IExpression { + memread.addressExpression = memread.addressExpression.process(this) + return memread + } + + fun process(memwrite: DirectMemoryWrite): IExpression { + memwrite.addressExpression = memwrite.addressExpression.process(this) + return memwrite + } + + fun process(addressOf: AddressOf): IExpression { + addressOf.identifier.process(this) + return addressOf + } + + fun process(inlineAssembly: InlineAssembly): IStatement { + return inlineAssembly + } +} diff --git a/compiler/src/prog8/ast/ImportedAstChecker.kt b/compiler/src/prog8/ast/processing/ImportedAstChecker.kt similarity index 78% rename from compiler/src/prog8/ast/ImportedAstChecker.kt rename to compiler/src/prog8/ast/processing/ImportedAstChecker.kt index 839c67f37..67938ea4e 100644 --- a/compiler/src/prog8/ast/ImportedAstChecker.kt +++ b/compiler/src/prog8/ast/processing/ImportedAstChecker.kt @@ -1,18 +1,11 @@ -package prog8.ast +package prog8.ast.processing +import prog8.ast.* +import prog8.ast.base.SyntaxError +import prog8.ast.base.printWarning +import prog8.ast.statements.Directive -/** - * Checks that are specific for imported modules. - */ - -internal fun Module.checkImportedValid() { - val checker = ImportedAstChecker() - checker.process(this) - printErrors(checker.result(), name) -} - - -private class ImportedAstChecker : IAstProcessor { +internal class ImportedAstChecker : IAstProcessor { private val checkResult: MutableList = mutableListOf() internal fun result(): List { diff --git a/compiler/src/prog8/ast/StmtReorderer.kt b/compiler/src/prog8/ast/processing/StatementReorderer.kt similarity index 69% rename from compiler/src/prog8/ast/StmtReorderer.kt rename to compiler/src/prog8/ast/processing/StatementReorderer.kt index e5a18d310..1807c363d 100644 --- a/compiler/src/prog8/ast/StmtReorderer.kt +++ b/compiler/src/prog8/ast/processing/StatementReorderer.kt @@ -1,19 +1,17 @@ -package prog8.ast +package prog8.ast.processing +import prog8.ast.* +import prog8.ast.base.DataType +import prog8.ast.base.FatalAstException +import prog8.ast.base.initvarsSubName +import prog8.ast.base.printWarning +import prog8.ast.expressions.BinaryExpression +import prog8.ast.expressions.FunctionCall +import prog8.ast.expressions.TypecastExpression +import prog8.ast.statements.* import prog8.functions.BuiltinFunctions -internal fun Program.reorderStatements() { - val initvalueCreator = VarInitValueAndAddressOfCreator(namespace) - initvalueCreator.process(this) - - val checker = StatementReorderer(this) - checker.process(this) -} - -internal const val initvarsSubName="prog8_init_vars" // the name of the subroutine that should be called for every block to initialize its variables - - -private class StatementReorderer(private val program: Program): IAstProcessor { +internal class StatementReorderer(private val program: Program): IAstProcessor { // Reorders the statements in a way the compiler needs. // - 'main' block must be the very first statement UNLESS it has an address set. // - blocks are ordered by address, where blocks without address are put at the end. @@ -144,7 +142,7 @@ private class StatementReorderer(private val program: Program): IAstProcessor { // add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine. // and if an assembly block doesn't contain a rts/rti if(subroutine.asmAddress==null && subroutine.amountOfRtsInAsm()==0) { - if (subroutine.statements.lastOrNull {it !is VarDecl} !is Return) { + if (subroutine.statements.lastOrNull {it !is VarDecl } !is Return) { val returnStmt = Return(emptyList(), subroutine.position) returnStmt.linkParents(subroutine) subroutine.statements.add(returnStmt) @@ -237,8 +235,7 @@ private class StatementReorderer(private val program: Program): IAstProcessor { private fun checkFunctionCallArguments(call: IFunctionCall, scope: INameScope) { // see if a typecast is needed to convert the arguments into the required parameter's type - val sub = call.target.targetStatement(scope) - when(sub) { + when(val sub = call.target.targetStatement(scope)) { is Subroutine -> { for(arg in sub.parameters.zip(call.arglist.withIndex())) { val argtype = arg.second.value.inferType(program) @@ -312,119 +309,3 @@ private class StatementReorderer(private val program: Program): IAstProcessor { return super.process(typecast) } } - - -private class VarInitValueAndAddressOfCreator(private val namespace: INameScope): IAstProcessor { - // For VarDecls that declare an initialization value: - // Replace the vardecl with an assignment (to set the initial value), - // and add a new vardecl with the default constant value of that type (usually zero) to the scope. - // This makes sure the variables get reset to the intended value on a next run of the program. - // Variable decls without a value don't get this treatment, which means they retain the last - // value they had when restarting the program. - // This is done in a separate step because it interferes with the namespace lookup of symbols - // in other ast processors. - - // Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc). - - private val vardeclsToAdd = mutableMapOf>() - - override fun process(module: Module) { - vardeclsToAdd.clear() - super.process(module) - - // add any new vardecls to the various scopes - for(decl in vardeclsToAdd) - for(d in decl.value) { - d.value.linkParents(decl.key as Node) - decl.key.statements.add(0, d.value) - } - } - - override fun process(decl: VarDecl): IStatement { - super.process(decl) - if(decl.type!=VarDeclType.VAR || decl.value==null) - return decl - - if(decl.datatype in NumericDatatypes) { - val scope = decl.definingScope() - addVarDecl(scope, decl.asDefaultValueDecl(null)) - val declvalue = decl.value!! - val value = - if(declvalue is LiteralValue) { - val converted = declvalue.intoDatatype(decl.datatype) - converted ?: declvalue - } - else - declvalue - val identifierName = listOf(decl.name) // // TODO this was: (scoped name) decl.scopedname.split(".") - return VariableInitializationAssignment( - AssignTarget(null, IdentifierReference(identifierName, decl.position), null, null, decl.position), - null, - value, - decl.position - ) - } - return decl - } - - override fun process(functionCall: FunctionCall): IExpression { - val targetStatement = functionCall.target.targetSubroutine(namespace) - if(targetStatement!=null) { - var node: Node = functionCall - while(node !is IStatement) - node=node.parent - addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, node) - } - return functionCall - } - - override fun process(functionCallStatement: FunctionCallStatement): IStatement { - val targetStatement = functionCallStatement.target.targetSubroutine(namespace) - if(targetStatement!=null) - addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement) - return functionCallStatement - } - - private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList, parent: IStatement) { - // functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead. - for(argparam in subroutine.parameters.withIndex().zip(arglist)) { - if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type in StringDatatypes) { - if(argparam.second is AddressOf) - continue - val idref = argparam.second as? IdentifierReference - val strvalue = argparam.second as? LiteralValue - if(idref!=null) { - val variable = idref.targetVarDecl(namespace) - if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) { - val pointerExpr = AddressOf(idref, idref.position) - pointerExpr.scopedname = parent.makeScopedName(idref.nameInSource.single()) - pointerExpr.linkParents(arglist[argparam.first.index].parent) - arglist[argparam.first.index] = pointerExpr - } - } - else if(strvalue!=null) { - if(strvalue.isString) { - // replace the argument with &autovar - val autoVarName = "$autoHeapValuePrefix${strvalue.heapId}" - val autoHeapvarRef = IdentifierReference(listOf(autoVarName), strvalue.position) - val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position) - pointerExpr.scopedname = parent.makeScopedName(autoVarName) - pointerExpr.linkParents(arglist[argparam.first.index].parent) - arglist[argparam.first.index] = pointerExpr - // add a vardecl so that the autovar can be resolved in later lookups - val variable = VarDecl(VarDeclType.VAR, strvalue.type, false, null, autoVarName, strvalue, - isArray = false, autoGenerated = false, position=strvalue.position) - addVarDecl(strvalue.definingScope(), variable) - } - } - } - } - } - - private fun addVarDecl(scope: INameScope, variable: VarDecl) { - if(scope !in vardeclsToAdd) - vardeclsToAdd[scope] = mutableMapOf() - vardeclsToAdd.getValue(scope)[variable.name]=variable - } - -} diff --git a/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt b/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt new file mode 100644 index 000000000..12d6c88a8 --- /dev/null +++ b/compiler/src/prog8/ast/processing/VarInitValueAndAddressOfCreator.kt @@ -0,0 +1,125 @@ +package prog8.ast.processing + +import prog8.ast.* +import prog8.ast.base.* +import prog8.ast.base.autoHeapValuePrefix +import prog8.ast.expressions.AddressOf +import prog8.ast.expressions.FunctionCall +import prog8.ast.expressions.IdentifierReference +import prog8.ast.expressions.LiteralValue +import prog8.ast.statements.* + +internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope): IAstProcessor { + // For VarDecls that declare an initialization value: + // Replace the vardecl with an assignment (to set the initial value), + // and add a new vardecl with the default constant value of that type (usually zero) to the scope. + // This makes sure the variables get reset to the intended value on a next run of the program. + // Variable decls without a value don't get this treatment, which means they retain the last + // value they had when restarting the program. + // This is done in a separate step because it interferes with the namespace lookup of symbols + // in other ast processors. + + // Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc). + + private val vardeclsToAdd = mutableMapOf>() + + override fun process(module: Module) { + vardeclsToAdd.clear() + super.process(module) + + // add any new vardecls to the various scopes + for(decl in vardeclsToAdd) + for(d in decl.value) { + d.value.linkParents(decl.key as Node) + decl.key.statements.add(0, d.value) + } + } + + override fun process(decl: VarDecl): IStatement { + super.process(decl) + if(decl.type!= VarDeclType.VAR || decl.value==null) + return decl + + if(decl.datatype in NumericDatatypes) { + val scope = decl.definingScope() + addVarDecl(scope, decl.asDefaultValueDecl(null)) + val declvalue = decl.value!! + val value = + if(declvalue is LiteralValue) { + val converted = declvalue.intoDatatype(decl.datatype) + converted ?: declvalue + } + else + declvalue + val identifierName = listOf(decl.name) // // TODO this was: (scoped name) decl.scopedname.split(".") + return VariableInitializationAssignment( + AssignTarget(null, IdentifierReference(identifierName, decl.position), null, null, decl.position), + null, + value, + decl.position + ) + } + return decl + } + + override fun process(functionCall: FunctionCall): IExpression { + val targetStatement = functionCall.target.targetSubroutine(namespace) + if(targetStatement!=null) { + var node: Node = functionCall + while(node !is IStatement) + node=node.parent + addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, node) + } + return functionCall + } + + override fun process(functionCallStatement: FunctionCallStatement): IStatement { + val targetStatement = functionCallStatement.target.targetSubroutine(namespace) + if(targetStatement!=null) + addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement) + return functionCallStatement + } + + private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList, parent: IStatement) { + // functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead. + for(argparam in subroutine.parameters.withIndex().zip(arglist)) { + if(argparam.first.value.type== DataType.UWORD || argparam.first.value.type in StringDatatypes) { + if(argparam.second is AddressOf) + continue + val idref = argparam.second as? IdentifierReference + val strvalue = argparam.second as? LiteralValue + if(idref!=null) { + val variable = idref.targetVarDecl(namespace) + if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) { + val pointerExpr = AddressOf(idref, idref.position) + pointerExpr.scopedname = parent.makeScopedName(idref.nameInSource.single()) + pointerExpr.linkParents(arglist[argparam.first.index].parent) + arglist[argparam.first.index] = pointerExpr + } + } + else if(strvalue!=null) { + if(strvalue.isString) { + // replace the argument with &autovar + val autoVarName = "$autoHeapValuePrefix${strvalue.heapId}" + val autoHeapvarRef = IdentifierReference(listOf(autoVarName), strvalue.position) + val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position) + pointerExpr.scopedname = parent.makeScopedName(autoVarName) + pointerExpr.linkParents(arglist[argparam.first.index].parent) + arglist[argparam.first.index] = pointerExpr + // add a vardecl so that the autovar can be resolved in later lookups + val variable = VarDecl(VarDeclType.VAR, strvalue.type, false, null, autoVarName, strvalue, + isArray = false, autoGenerated = false, position = strvalue.position) + addVarDecl(strvalue.definingScope(), variable) + } + } + } + } + } + + private fun addVarDecl(scope: INameScope, variable: VarDecl) { + if(scope !in vardeclsToAdd) + vardeclsToAdd[scope] = mutableMapOf() + vardeclsToAdd.getValue(scope)[variable.name]=variable + } + +} diff --git a/compiler/src/prog8/ast/statements/AstStatements.kt b/compiler/src/prog8/ast/statements/AstStatements.kt new file mode 100644 index 000000000..ce7b70ae2 --- /dev/null +++ b/compiler/src/prog8/ast/statements/AstStatements.kt @@ -0,0 +1,639 @@ +package prog8.ast.statements + +import prog8.ast.* +import prog8.ast.base.* +import prog8.ast.expressions.* +import prog8.ast.processing.IAstProcessor +import prog8.compiler.HeapValues + + +class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : IStatement { + override var parent: Node = ParentSentinel + override fun linkParents(parent: Node) {} + override fun process(processor: IAstProcessor): IStatement = this + override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder + override val expensiveToInline = false +} + + +data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean?) + + +class Block(override val name: String, + val address: Int?, + override var statements: MutableList, + val isInLibrary: Boolean, + override val position: Position) : IStatement, INameScope { + override lateinit var parent: Node + override val expensiveToInline + get() = statements.any { it.expensiveToInline } + + override fun linkParents(parent: Node) { + this.parent = parent + statements.forEach {it.linkParents(this)} + } + + override fun process(processor: IAstProcessor) = processor.process(this) + + override fun toString(): String { + return "Block(name=$name, address=$address, ${statements.size} statements)" + } + + fun options() = statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.map {it.name!!}.toSet() +} + +data class Directive(val directive: String, val args: List, override val position: Position) : IStatement { + override lateinit var parent: Node + override val expensiveToInline = false + + override fun linkParents(parent: Node) { + this.parent = parent + args.forEach{it.linkParents(this)} + } + + override fun process(processor: IAstProcessor) = processor.process(this) +} + +data class DirectiveArg(val str: String?, val name: String?, val int: Int?, override val position: Position) : Node { + override lateinit var parent: Node + + override fun linkParents(parent: Node) { + this.parent = parent + } +} + +data class Label(val name: String, override val position: Position) : IStatement { + override lateinit var parent: Node + override val expensiveToInline = false + + override fun linkParents(parent: Node) { + this.parent = parent + } + + override fun process(processor: IAstProcessor) = processor.process(this) + + override fun toString(): String { + return "Label(name=$name, pos=$position)" + } + + val scopedname: String by lazy { makeScopedName(name) } +} + +open class Return(var values: List, override val position: Position) : IStatement { + override lateinit var parent: Node + override val expensiveToInline = values.any { it !is LiteralValue } + + override fun linkParents(parent: Node) { + this.parent = parent + values.forEach {it.linkParents(this)} + } + + override fun process(processor: IAstProcessor) = processor.process(this) + + override fun toString(): String { + return "Return(values: $values, pos=$position)" + } +} + +class ReturnFromIrq(override val position: Position) : Return(emptyList(), position) { + override fun process(processor: IAstProcessor) = this + + override fun toString(): String { + return "ReturnFromIrq(pos=$position)" + } +} + +class Continue(override val position: Position) : IStatement { + override lateinit var parent: Node + override val expensiveToInline = false + + override fun linkParents(parent: Node) { + this.parent=parent + } + + override fun process(processor: IAstProcessor) = processor.process(this) +} + +class Break(override val position: Position) : IStatement { + override lateinit var parent: Node + override val expensiveToInline = false + + override fun linkParents(parent: Node) { + this.parent=parent + } + + override fun process(processor: IAstProcessor) = processor.process(this) +} + +class VarDecl(val type: VarDeclType, + private val declaredDatatype: DataType, + val zeropage: Boolean, + var arraysize: ArrayIndex?, + val name: String, + var value: IExpression?, + val isArray: Boolean, + val autoGenerated: Boolean, + override val position: Position) : IStatement { + override lateinit var parent: Node + override val expensiveToInline + get() = value!=null && value !is LiteralValue + + val datatypeErrors = mutableListOf() // don't crash at init time, report them in the AstChecker + val datatype = + if (!isArray) declaredDatatype + else when (declaredDatatype) { + DataType.UBYTE -> DataType.ARRAY_UB + DataType.BYTE -> DataType.ARRAY_B + DataType.UWORD -> DataType.ARRAY_UW + DataType.WORD -> DataType.ARRAY_W + DataType.FLOAT -> DataType.ARRAY_F + else -> { + datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats", position)) + DataType.UBYTE + } + } + + override fun linkParents(parent: Node) { + this.parent = parent + arraysize?.linkParents(this) + value?.linkParents(this) + } + + override fun process(processor: IAstProcessor) = processor.process(this) + + val scopedname: String by lazy { makeScopedName(name) } + + override fun toString(): String { + return "VarDecl(name=$name, vartype=$type, datatype=$datatype, array=$isArray, value=$value, pos=$position)" + } + + fun asDefaultValueDecl(parent: Node?): VarDecl { + val constValue = when(declaredDatatype) { + DataType.UBYTE -> LiteralValue(DataType.UBYTE, 0, position = position) + DataType.BYTE -> LiteralValue(DataType.BYTE, 0, position = position) + DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = 0, position = position) + DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = 0, position = position) + DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = 0.0, position = position) + else -> throw FatalAstException("can only set a default value for a numeric type") + } + val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, constValue, isArray, true, position) + if(parent!=null) + decl.linkParents(parent) + return decl + } +} + +class ArrayIndex(var index: IExpression, override val position: Position) : Node { + override lateinit var parent: Node + + override fun linkParents(parent: Node) { + this.parent = parent + index.linkParents(this) + } + + companion object { + fun forArray(v: LiteralValue, heap: HeapValues): ArrayIndex { + val arraySize = v.arrayvalue?.size ?: heap.get(v.heapId!!).arraysize + return ArrayIndex(LiteralValue.optimalNumeric(arraySize, v.position), v.position) + } + } + + fun process(processor: IAstProcessor) { + index = index.process(processor) + } + + override fun toString(): String { + return("ArrayIndex($index, pos=$position)") + } + + fun size() = (index as? LiteralValue)?.asIntegerValue +} + +open class Assignment(var targets: List, val aug_op : String?, var value: IExpression, override val position: Position) : IStatement { + override lateinit var parent: Node + override val expensiveToInline + get() = value !is LiteralValue + + override fun linkParents(parent: Node) { + this.parent = parent + targets.forEach { it.linkParents(this) } + value.linkParents(this) + } + + override fun process(processor: IAstProcessor) = processor.process(this) + + override fun toString(): String { + return("Assignment(augop: $aug_op, targets: $targets, value: $value, pos=$position)") + } + + val singleTarget: AssignTarget? + get() { + return targets.singleOrNull() // common case + } +} + +// This is a special class so the compiler can see if the assignments are for initializing the vars in the scope, +// or just a regular assignment. It may optimize the initialization step from this. +class VariableInitializationAssignment(target: AssignTarget, aug_op: String?, value: IExpression, position: Position) + : Assignment(listOf(target), aug_op, value, position) + +data class AssignTarget(val register: Register?, + val identifier: IdentifierReference?, + val arrayindexed: ArrayIndexedExpression?, + var memoryAddress: DirectMemoryWrite?, + override val position: Position) : Node { + override lateinit var parent: Node + + override fun linkParents(parent: Node) { + this.parent = parent + identifier?.linkParents(this) + arrayindexed?.linkParents(this) + memoryAddress?.linkParents(this) + } + + fun process(processor: IAstProcessor) = processor.process(this) + + companion object { + fun fromExpr(expr: IExpression): AssignTarget { + return when (expr) { + is RegisterExpr -> AssignTarget(expr.register, null, null, null, expr.position) + is IdentifierReference -> AssignTarget(null, expr, null, null, expr.position) + is ArrayIndexedExpression -> AssignTarget(null, null, expr, null, expr.position) + is DirectMemoryRead -> AssignTarget(null, null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position) + is DirectMemoryWrite -> AssignTarget(null, null, null, expr, expr.position) + else -> throw FatalAstException("invalid expression object $expr") + } + } + } + + fun inferType(program: Program, stmt: IStatement): DataType? { + if(register!=null) + return DataType.UBYTE + + if(identifier!=null) { + val symbol = program.namespace.lookup(identifier.nameInSource, stmt) ?: return null + if (symbol is VarDecl) return symbol.datatype + } + + if(arrayindexed!=null) { + val dt = arrayindexed.inferType(program) + if(dt!=null) + return dt + } + + if(memoryAddress!=null) + return DataType.UBYTE + + return null + } + + fun shortString(withTypePrefix: Boolean=false): String { + if(register!=null) + return (if(withTypePrefix) "0register::" else "") + register.name + if(identifier!=null) + return (if(withTypePrefix) "3identifier::" else "") + identifier.nameInSource.last() + if(arrayindexed!=null) + return (if(withTypePrefix) "2arrayidx::" else "") + arrayindexed.identifier.nameInSource.last() + val address = memoryAddress?.addressExpression + if(address is LiteralValue) + return (if(withTypePrefix) "1address::" else "") +address.asIntegerValue.toString() + return if(withTypePrefix) "???::???" else "???" + } + + fun isMemoryMapped(namespace: INameScope): Boolean = + memoryAddress!=null || (identifier?.targetVarDecl(namespace)?.type== VarDeclType.MEMORY) + + infix fun isSameAs(value: IExpression): Boolean { + return when { + this.memoryAddress!=null -> false + this.register!=null -> value is RegisterExpr && value.register==register + this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier.nameInSource + this.arrayindexed!=null -> value is ArrayIndexedExpression && + value.identifier.nameInSource==arrayindexed.identifier.nameInSource && + value.arrayspec.size()!=null && + arrayindexed.arrayspec.size()!=null && + value.arrayspec.size()==arrayindexed.arrayspec.size() + else -> false + } + } + + fun isSameAs(other: AssignTarget, program: Program): Boolean { + if(this===other) + return true + if(this.register!=null && other.register!=null) + return this.register==other.register + if(this.identifier!=null && other.identifier!=null) + return this.identifier.nameInSource==other.identifier.nameInSource + if(this.memoryAddress!=null && other.memoryAddress!=null) { + val addr1 = this.memoryAddress!!.addressExpression.constValue(program) + val addr2 = other.memoryAddress!!.addressExpression.constValue(program) + return addr1!=null && addr2!=null && addr1==addr2 + } + if(this.arrayindexed!=null && other.arrayindexed!=null) { + if(this.arrayindexed.identifier.nameInSource == other.arrayindexed.identifier.nameInSource) { + val x1 = this.arrayindexed.arrayspec.index.constValue(program) + val x2 = other.arrayindexed.arrayspec.index.constValue(program) + return x1!=null && x2!=null && x1==x2 + } + } + return false + } + + fun isNotMemory(namespace: INameScope): Boolean { + if(this.register!=null) + return true + if(this.memoryAddress!=null) + return false + if(this.arrayindexed!=null) { + val targetStmt = this.arrayindexed.identifier.targetVarDecl(namespace) + if(targetStmt!=null) + return targetStmt.type!= VarDeclType.MEMORY + } + if(this.identifier!=null) { + val targetStmt = this.identifier.targetVarDecl(namespace) + if(targetStmt!=null) + return targetStmt.type!= VarDeclType.MEMORY + } + return false + } +} + +class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : IStatement { + override lateinit var parent: Node + override val expensiveToInline = false + + override fun linkParents(parent: Node) { + this.parent = parent + target.linkParents(this) + } + + override fun process(processor: IAstProcessor) = processor.process(this) + + override fun toString(): String { + return "PostIncrDecr(op: $operator, target: $target, pos=$position)" + } +} + +class Jump(val address: Int?, + val identifier: IdentifierReference?, + val generatedLabel: String?, // used in code generation scenarios + override val position: Position) : IStatement { + override lateinit var parent: Node + override val expensiveToInline = false + + override fun linkParents(parent: Node) { + this.parent = parent + identifier?.linkParents(this) + } + + override fun process(processor: IAstProcessor) = processor.process(this) + + override fun toString(): String { + return "Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)" + } +} + +class FunctionCallStatement(override var target: IdentifierReference, + override var arglist: MutableList, + override val position: Position) : IStatement, IFunctionCall { + override lateinit var parent: Node + override val expensiveToInline + get() = arglist.any { it !is LiteralValue } + + override fun linkParents(parent: Node) { + this.parent = parent + target.linkParents(this) + arglist.forEach { it.linkParents(this) } + } + + override fun process(processor: IAstProcessor) = processor.process(this) + + override fun toString(): String { + return "FunctionCallStatement(target=$target, pos=$position)" + } +} + +class InlineAssembly(val assembly: String, override val position: Position) : IStatement { + override lateinit var parent: Node + override val expensiveToInline = true + + override fun linkParents(parent: Node) { + this.parent = parent + } + + override fun process(processor: IAstProcessor) = processor.process(this) +} + +class AnonymousScope(override var statements: MutableList, + override val position: Position) : INameScope, IStatement { + override val name: String + override lateinit var parent: Node + override val expensiveToInline + get() = statements.any { it.expensiveToInline } + + init { + name = "" // make sure it's an invalid soruce code identifier so user source code can never produce it + 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 NopStatement(override val position: Position): IStatement { + override lateinit var parent: Node + override val expensiveToInline = false + + override fun linkParents(parent: Node) { + this.parent = parent + } + + override fun process(processor: IAstProcessor) = this +} + +// the subroutine class covers both the normal user-defined subroutines, +// and also the predefined/ROM/register-based subroutines. +// (multiple return types can only occur for the latter type) +class Subroutine(override val name: String, + val parameters: List, + val returntypes: List, + val asmParameterRegisters: List, + val asmReturnvaluesRegisters: List, + val asmClobbers: Set, + val asmAddress: Int?, + val isAsmSubroutine: Boolean, + override var statements: MutableList, + override val position: Position) : IStatement, INameScope { + + var keepAlways: Boolean = false + override val expensiveToInline + get() = statements.any { it.expensiveToInline } + + override lateinit var parent: Node + val calledBy = mutableListOf() + val calls = mutableSetOf() + + val scopedname: String by lazy { makeScopedName(name) } + + override fun linkParents(parent: Node) { + this.parent = parent + parameters.forEach { it.linkParents(this) } + statements.forEach { it.linkParents(this) } + } + + override fun process(processor: IAstProcessor) = processor.process(this) + + override fun toString(): String { + return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)" + } + + fun amountOfRtsInAsm(): Int = statements + .asSequence() + .filter { it is InlineAssembly } + .map { (it as InlineAssembly).assembly } + .count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it } + + val canBeAsmSubroutine =false // TODO disabled for now, see below about problem with converting to asm subroutine +// !isAsmSubroutine +// && ((parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE, DataType.WORD, DataType.UWORD)) +// || (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE })) + + fun intoAsmSubroutine(): Subroutine { + // TODO turn subroutine into asm calling convention. Requires rethinking of how parameters are handled (conflicts with local vardefs now, see AstIdentifierChecker...) + return this // TODO + +// println("TO ASM $this") // TODO +// val paramregs = if (parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE)) +// listOf(RegisterOrStatusflag(RegisterOrPair.Y, null, null)) +// else if (parameters.size == 1 && parameters[0].type in setOf(DataType.WORD, DataType.UWORD)) +// listOf(RegisterOrStatusflag(RegisterOrPair.AY, null, null)) +// else if (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE }) +// listOf(RegisterOrStatusflag(RegisterOrPair.A, null, null), RegisterOrStatusflag(RegisterOrPair.Y, null, null)) +// else throw FatalAstException("cannot convert subroutine to asm parameters") +// +// val asmsub=Subroutine( +// name, +// parameters, +// returntypes, +// paramregs, +// emptyList(), +// emptySet(), +// null, +// true, +// statements, +// position +// ) +// asmsub.linkParents(parent) +// return asmsub + } +} + +open class SubroutineParameter(val name: String, + val type: DataType, + override val position: Position) : Node { + override lateinit var parent: Node + + override fun linkParents(parent: Node) { + this.parent = parent + } +} + +class IfStatement(var condition: IExpression, + var truepart: AnonymousScope, + var elsepart: AnonymousScope, + override val position: Position) : IStatement { + override lateinit var parent: Node + override val expensiveToInline: Boolean + get() = truepart.expensiveToInline || elsepart.expensiveToInline + + override fun linkParents(parent: Node) { + this.parent = parent + condition.linkParents(this) + truepart.linkParents(this) + elsepart.linkParents(this) + } + + override fun process(processor: IAstProcessor): IStatement = processor.process(this) +} + +class BranchStatement(var condition: BranchCondition, + var truepart: AnonymousScope, + var elsepart: AnonymousScope, + override val position: Position) : IStatement { + override lateinit var parent: Node + override val expensiveToInline: Boolean + get() = truepart.expensiveToInline || elsepart.expensiveToInline + + override fun linkParents(parent: Node) { + this.parent = parent + truepart.linkParents(this) + elsepart.linkParents(this) + } + + override fun process(processor: IAstProcessor): IStatement = processor.process(this) +} + +class ForLoop(val loopRegister: Register?, + val decltype: DataType?, + val zeropage: Boolean, + val loopVar: IdentifierReference?, + var iterable: IExpression, + var body: AnonymousScope, + override val position: Position) : IStatement { + override lateinit var parent: Node + override val expensiveToInline = true + + override fun linkParents(parent: Node) { + this.parent=parent + loopVar?.linkParents(if(decltype==null) this else body) + iterable.linkParents(this) + body.linkParents(this) + } + + override fun process(processor: IAstProcessor) = processor.process(this) + + override fun toString(): String { + return "ForLoop(loopVar: $loopVar, loopReg: $loopRegister, iterable: $iterable, pos=$position)" + } + + companion object { + const val iteratorLoopcounterVarname = "prog8forloopcounter" + } +} + +class WhileLoop(var condition: IExpression, + var body: AnonymousScope, + override val position: Position) : IStatement { + override lateinit var parent: Node + override val expensiveToInline = true + + override fun linkParents(parent: Node) { + this.parent = parent + condition.linkParents(this) + body.linkParents(this) + } + + override fun process(processor: IAstProcessor): IStatement = processor.process(this) +} + +class RepeatLoop(var body: AnonymousScope, + var untilCondition: IExpression, + override val position: Position) : IStatement { + override lateinit var parent: Node + override val expensiveToInline = true + + override fun linkParents(parent: Node) { + this.parent = parent + untilCondition.linkParents(this) + body.linkParents(this) + } + + override fun process(processor: IAstProcessor): IStatement = processor.process(this) +} diff --git a/compiler/src/prog8/astvm/AstVm.kt b/compiler/src/prog8/astvm/AstVm.kt index a27d75371..aa1b55fb5 100644 --- a/compiler/src/prog8/astvm/AstVm.kt +++ b/compiler/src/prog8/astvm/AstVm.kt @@ -1,6 +1,11 @@ package prog8.astvm import prog8.ast.* +import prog8.ast.base.* +import prog8.ast.base.initvarsSubName +import prog8.ast.expressions.IdentifierReference +import prog8.ast.expressions.LiteralValue +import prog8.ast.statements.* import prog8.compiler.RuntimeValue import prog8.compiler.RuntimeValueRange import prog8.compiler.target.c64.Petscii diff --git a/compiler/src/prog8/astvm/BuiltinFunctions.kt b/compiler/src/prog8/astvm/BuiltinFunctions.kt index 28c218242..c51263d21 100644 --- a/compiler/src/prog8/astvm/BuiltinFunctions.kt +++ b/compiler/src/prog8/astvm/BuiltinFunctions.kt @@ -1,6 +1,6 @@ package prog8.astvm -import prog8.ast.DataType +import prog8.ast.base.DataType import prog8.compiler.RuntimeValue import java.lang.Math.toDegrees import java.lang.Math.toRadians diff --git a/compiler/src/prog8/astvm/Expressions.kt b/compiler/src/prog8/astvm/Expressions.kt index 6cd89f32d..f39f63d58 100644 --- a/compiler/src/prog8/astvm/Expressions.kt +++ b/compiler/src/prog8/astvm/Expressions.kt @@ -1,6 +1,14 @@ package prog8.astvm import prog8.ast.* +import prog8.ast.base.ArrayElementTypes +import prog8.ast.base.DataType +import prog8.ast.base.VarDeclType +import prog8.ast.expressions.* +import prog8.ast.statements.BuiltinFunctionStatementPlaceholder +import prog8.ast.statements.Label +import prog8.ast.statements.Subroutine +import prog8.ast.statements.VarDecl import prog8.compiler.RuntimeValue import prog8.compiler.RuntimeValueRange import kotlin.math.abs diff --git a/compiler/src/prog8/astvm/VariablesCreator.kt b/compiler/src/prog8/astvm/VariablesCreator.kt index c01f55202..626990079 100644 --- a/compiler/src/prog8/astvm/VariablesCreator.kt +++ b/compiler/src/prog8/astvm/VariablesCreator.kt @@ -1,6 +1,10 @@ package prog8.astvm import prog8.ast.* +import prog8.ast.base.* +import prog8.ast.expressions.LiteralValue +import prog8.ast.processing.IAstProcessor +import prog8.ast.statements.VarDecl import prog8.compiler.HeapValues import prog8.compiler.RuntimeValue diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 0d2c76821..cbc4cca40 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -1,7 +1,11 @@ package prog8.compiler import prog8.ast.* -import prog8.ast.RegisterOrPair.* +import prog8.ast.base.* +import prog8.ast.base.RegisterOrPair.* +import prog8.ast.expressions.* +import prog8.ast.processing.IAstProcessor +import prog8.ast.statements.* import prog8.compiler.intermediate.IntermediateProgram import prog8.compiler.intermediate.Opcode import prog8.compiler.intermediate.branchOpcodes @@ -383,7 +387,7 @@ internal class Compiler(private val program: Program): IAstProcessor { // The compiler could then convert it to a special system call val sub = stmt.parent as? Subroutine val scopename = - if(sub!=null && sub.statements.filter{it !is VarDecl}.size==1) + if(sub!=null && sub.statements.filter{it !is VarDecl }.size==1) sub.scopedname else null @@ -1413,7 +1417,7 @@ internal class Compiler(private val program: Program): IAstProcessor { // @todo use convertType()???? when(targetDt) { in ByteDatatypes -> - if(valueDt!=DataType.BYTE && valueDt!=DataType.UBYTE) + if(valueDt!= DataType.BYTE && valueDt!= DataType.UBYTE) throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt") DataType.WORD -> { when (valueDt) { @@ -1619,11 +1623,11 @@ internal class Compiler(private val program: Program): IAstProcessor { } private fun translateForOverIterableVar(loop: ForLoop, loopvarDt: DataType, iterableValue: LiteralValue) { - if(loopvarDt==DataType.UBYTE && iterableValue.type !in setOf(DataType.STR, DataType.STR_S, DataType.ARRAY_UB)) + if(loopvarDt== DataType.UBYTE && iterableValue.type !in setOf(DataType.STR, DataType.STR_S, DataType.ARRAY_UB)) throw CompilerException("loop variable type doesn't match iterableValue type") - else if(loopvarDt==DataType.UWORD && iterableValue.type != DataType.ARRAY_UW) + else if(loopvarDt== DataType.UWORD && iterableValue.type != DataType.ARRAY_UW) throw CompilerException("loop variable type doesn't match iterableValue type") - else if(loopvarDt==DataType.FLOAT && iterableValue.type != DataType.ARRAY_F) + else if(loopvarDt== DataType.FLOAT && iterableValue.type != DataType.ARRAY_F) throw CompilerException("loop variable type doesn't match iterableValue type") val numElements: Int @@ -1645,7 +1649,7 @@ internal class Compiler(private val program: Program): IAstProcessor { else -> throw CompilerException("weird datatype") } - if(loop.loopRegister!=null && loop.loopRegister==Register.X) + if(loop.loopRegister!=null && loop.loopRegister== Register.X) throw CompilerException("loopVar cannot use X register because that is used as internal stack pointer") /** @@ -1848,10 +1852,10 @@ internal class Compiler(private val program: Program): IAstProcessor { val condition = if(literalStepValue > 0) { // if LV > last goto break - BinaryExpression(loopVar,">", range.to, range.position) + BinaryExpression(loopVar, ">", range.to, range.position) } else { // if LV < last goto break - BinaryExpression(loopVar,"<", range.to, range.position) + BinaryExpression(loopVar, "<", range.to, range.position) } val ifstmt = IfStatement(condition, AnonymousScope(mutableListOf(Jump(null, null, breakLabel, range.position)), range.position), @@ -1881,7 +1885,7 @@ internal class Compiler(private val program: Program): IAstProcessor { TODO("can't generate code for step other than 1 or -1 right now") // LV++ / LV-- - val postIncr = PostIncrDecr(lvTarget, if(step==1) "++" else "--", range.position) + val postIncr = PostIncrDecr(lvTarget, if (step == 1) "++" else "--", range.position) postIncr.linkParents(body) translate(postIncr) if(lvTarget.register!=null) @@ -2067,10 +2071,10 @@ internal class Compiler(private val program: Program): IAstProcessor { private fun translate(addrof: AddressOf) { val target = addrof.identifier.targetVarDecl(program.namespace)!! - if(target.datatype in ArrayDatatypes || target.datatype in StringDatatypes|| target.datatype==DataType.FLOAT) { + if(target.datatype in ArrayDatatypes || target.datatype in StringDatatypes || target.datatype== DataType.FLOAT) { pushHeapVarAddress(addrof.identifier, false) } - else if(target.datatype==DataType.FLOAT) { + else if(target.datatype== DataType.FLOAT) { pushFloatAddress(addrof.identifier) } else diff --git a/compiler/src/prog8/compiler/RuntimeValue.kt b/compiler/src/prog8/compiler/RuntimeValue.kt index 8b0d45203..51bf1a165 100644 --- a/compiler/src/prog8/compiler/RuntimeValue.kt +++ b/compiler/src/prog8/compiler/RuntimeValue.kt @@ -1,6 +1,7 @@ package prog8.compiler -import prog8.ast.* +import prog8.ast.base.* +import prog8.ast.expressions.LiteralValue import prog8.compiler.target.c64.Petscii import kotlin.math.abs import kotlin.math.pow @@ -174,7 +175,7 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?= DataType.UBYTE, DataType.UWORD -> { // storing a negative number in an unsigned one is done by storing the 2's complement instead val number = abs(result.toDouble().toInt()) - if(leftDt==DataType.UBYTE) + if(leftDt== DataType.UBYTE) RuntimeValue(DataType.UBYTE, (number xor 255) + 1) else RuntimeValue(DataType.UBYTE, (number xor 65535) + 1) diff --git a/compiler/src/prog8/compiler/Zeropage.kt b/compiler/src/prog8/compiler/Zeropage.kt index e247d2f7b..d980b534a 100644 --- a/compiler/src/prog8/compiler/Zeropage.kt +++ b/compiler/src/prog8/compiler/Zeropage.kt @@ -1,6 +1,7 @@ package prog8.compiler -import prog8.ast.* +import prog8.ast.base.* +import prog8.ast.base.printWarning class ZeropageDepletedError(message: String) : Exception(message) diff --git a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt index 18976d28f..e7f7d2f3f 100644 --- a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt +++ b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt @@ -1,6 +1,10 @@ package prog8.compiler.intermediate -import prog8.ast.* +import prog8.ast.antlr.escape +import prog8.ast.base.* +import prog8.ast.base.printWarning +import prog8.ast.expressions.LiteralValue +import prog8.ast.statements.VarDecl import prog8.compiler.RuntimeValue import prog8.compiler.CompilerException import prog8.compiler.HeapValues @@ -412,7 +416,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap VarDeclType.MEMORY -> { // note that constants are all folded away, but assembly code may still refer to them val lv = decl.value as LiteralValue - if(lv.type!=DataType.UWORD && lv.type!=DataType.UBYTE) + if(lv.type!= DataType.UWORD && lv.type!= DataType.UBYTE) throw CompilerException("expected integer memory address $lv") currentBlock.memoryPointers[scopedname] = Pair(lv.asIntegerValue!!, decl.datatype) } diff --git a/compiler/src/prog8/compiler/target/c64/AsmGen.kt b/compiler/src/prog8/compiler/target/c64/AsmGen.kt index eb9d8dded..1a60630d4 100644 --- a/compiler/src/prog8/compiler/target/c64/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/AsmGen.kt @@ -3,7 +3,10 @@ package prog8.compiler.target.c64 // note: to put stuff on the stack, we use Absolute,X addressing mode which is 3 bytes / 4 cycles // possible space optimization is to use zeropage (indirect),Y which is 2 bytes, but 5 cycles -import prog8.ast.* +import prog8.ast.antlr.escape +import prog8.ast.base.DataType +import prog8.ast.base.initvarsSubName +import prog8.ast.base.printWarning import prog8.compiler.RuntimeValue import prog8.compiler.* import prog8.compiler.intermediate.* @@ -63,7 +66,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter // make a list of all const floats that are used for(block in program.blocks) { - for(ins in block.instructions.filter{it.arg?.type==DataType.FLOAT}) { + for(ins in block.instructions.filter{it.arg?.type== DataType.FLOAT}) { val float = ins.arg!!.numericValue().toDouble() if(float !in globalFloatConsts) globalFloatConsts[float] = "prog8_const_float_${globalFloatConsts.size}" @@ -186,7 +189,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter out(" ldx #\$ff\t; init estack pointer") out(" ; initialize the variables in each block") for(block in program.blocks) { - val initVarsLabel = block.instructions.firstOrNull { it is LabelInstr && it.name==initvarsSubName } as? LabelInstr + val initVarsLabel = block.instructions.firstOrNull { it is LabelInstr && it.name== initvarsSubName } as? LabelInstr if(initVarsLabel!=null) out(" jsr ${block.name}.${initVarsLabel.name}") } @@ -362,10 +365,10 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter private fun makeArrayFillDataUnsigned(value: RuntimeValue): List { val array = heap.get(value.heapId!!).array!! return when { - value.type==DataType.ARRAY_UB -> + value.type== DataType.ARRAY_UB -> // byte array can never contain pointer-to types, so treat values as all integers array.map { "$"+it.integer!!.toString(16).padStart(2, '0') } - value.type==DataType.ARRAY_UW -> array.map { + value.type== DataType.ARRAY_UW -> array.map { when { it.integer!=null -> "$"+it.integer.toString(16).padStart(2, '0') it.addressOf!=null -> symname(it.addressOf.scopedname!!, block) diff --git a/compiler/src/prog8/functions/BuiltinFunctions.kt b/compiler/src/prog8/functions/BuiltinFunctions.kt index e0e3c3f2e..53fe7550a 100644 --- a/compiler/src/prog8/functions/BuiltinFunctions.kt +++ b/compiler/src/prog8/functions/BuiltinFunctions.kt @@ -1,6 +1,11 @@ package prog8.functions import prog8.ast.* +import prog8.ast.base.* +import prog8.ast.expressions.DirectMemoryRead +import prog8.ast.expressions.IdentifierReference +import prog8.ast.expressions.LiteralValue +import prog8.ast.statements.VarDecl import prog8.compiler.CompilerException import kotlin.math.* @@ -111,13 +116,13 @@ fun builtinFunctionReturnType(function: String, args: List, program fun datatypeFromIterableArg(arglist: IExpression): DataType { if(arglist is LiteralValue) { - if(arglist.type==DataType.ARRAY_UB || arglist.type==DataType.ARRAY_UW || arglist.type==DataType.ARRAY_F) { + if(arglist.type== DataType.ARRAY_UB || arglist.type== DataType.ARRAY_UW || arglist.type== DataType.ARRAY_F) { val dt = arglist.arrayvalue!!.map {it.inferType(program)} - if(dt.any { it!=DataType.UBYTE && it!=DataType.UWORD && it!=DataType.FLOAT}) { + if(dt.any { it!= DataType.UBYTE && it!= DataType.UWORD && it!= DataType.FLOAT}) { throw FatalAstException("fuction $function only accepts arraysize of numeric values") } - if(dt.any { it==DataType.FLOAT }) return DataType.FLOAT - if(dt.any { it==DataType.UWORD }) return DataType.UWORD + if(dt.any { it== DataType.FLOAT }) return DataType.FLOAT + if(dt.any { it== DataType.UWORD }) return DataType.UWORD return DataType.UBYTE } } @@ -186,7 +191,7 @@ private fun oneDoubleArg(args: List, position: Position, program: P if(args.size!=1) throw SyntaxError("built-in function requires one floating point argument", position) val constval = args[0].constValue(program) ?: throw NotConstArgumentException() - if(constval.type!=DataType.FLOAT) + if(constval.type!= DataType.FLOAT) throw SyntaxError("built-in function requires one floating point argument", position) val float = constval.asNumericValue?.toDouble()!! @@ -197,16 +202,16 @@ private fun oneDoubleArgOutputWord(args: List, position: Position, if(args.size!=1) throw SyntaxError("built-in function requires one floating point argument", position) val constval = args[0].constValue(program) ?: throw NotConstArgumentException() - if(constval.type!=DataType.FLOAT) + if(constval.type!= DataType.FLOAT) throw SyntaxError("built-in function requires one floating point argument", position) - return LiteralValue(DataType.WORD, wordvalue=function(constval.asNumericValue!!.toDouble()).toInt(), position=args[0].position) + return LiteralValue(DataType.WORD, wordvalue = function(constval.asNumericValue!!.toDouble()).toInt(), position = args[0].position) } private fun oneIntArgOutputInt(args: List, position: Position, program: Program, function: (arg: Int)->Number): LiteralValue { if(args.size!=1) throw SyntaxError("built-in function requires one integer argument", position) val constval = args[0].constValue(program) ?: throw NotConstArgumentException() - if(constval.type!=DataType.UBYTE && constval.type!=DataType.UWORD) + if(constval.type!= DataType.UBYTE && constval.type!= DataType.UWORD) throw SyntaxError("built-in function requires one integer argument", position) val integer = constval.asNumericValue?.toInt()!! @@ -378,7 +383,7 @@ private fun builtinSin8(args: List, position: Position, program: Pr throw SyntaxError("sin8 requires one argument", position) val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI - return LiteralValue(DataType.BYTE, bytevalue = (127.0* sin(rad)).toShort(), position = position) + return LiteralValue(DataType.BYTE, bytevalue = (127.0 * sin(rad)).toShort(), position = position) } private fun builtinSin8u(args: List, position: Position, program: Program): LiteralValue { @@ -386,7 +391,7 @@ private fun builtinSin8u(args: List, position: Position, program: P throw SyntaxError("sin8u requires one argument", position) val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI - return LiteralValue(DataType.UBYTE, bytevalue = (128.0+127.5*sin(rad)).toShort(), position = position) + return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5 * sin(rad)).toShort(), position = position) } private fun builtinCos8(args: List, position: Position, program: Program): LiteralValue { @@ -394,7 +399,7 @@ private fun builtinCos8(args: List, position: Position, program: Pr throw SyntaxError("cos8 requires one argument", position) val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI - return LiteralValue(DataType.BYTE, bytevalue = (127.0* cos(rad)).toShort(), position = position) + return LiteralValue(DataType.BYTE, bytevalue = (127.0 * cos(rad)).toShort(), position = position) } private fun builtinCos8u(args: List, position: Position, program: Program): LiteralValue { @@ -402,7 +407,7 @@ private fun builtinCos8u(args: List, position: Position, program: P throw SyntaxError("cos8u requires one argument", position) val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI - return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5*cos(rad)).toShort(), position = position) + return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5 * cos(rad)).toShort(), position = position) } private fun builtinSin16(args: List, position: Position, program: Program): LiteralValue { @@ -410,7 +415,7 @@ private fun builtinSin16(args: List, position: Position, program: P throw SyntaxError("sin16 requires one argument", position) val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI - return LiteralValue(DataType.WORD, wordvalue = (32767.0* sin(rad)).toInt(), position = position) + return LiteralValue(DataType.WORD, wordvalue = (32767.0 * sin(rad)).toInt(), position = position) } private fun builtinSin16u(args: List, position: Position, program: Program): LiteralValue { @@ -418,7 +423,7 @@ private fun builtinSin16u(args: List, position: Position, program: throw SyntaxError("sin16u requires one argument", position) val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI - return LiteralValue(DataType.UWORD, wordvalue = (32768.0+32767.5*sin(rad)).toInt(), position = position) + return LiteralValue(DataType.UWORD, wordvalue = (32768.0 + 32767.5 * sin(rad)).toInt(), position = position) } private fun builtinCos16(args: List, position: Position, program: Program): LiteralValue { @@ -426,7 +431,7 @@ private fun builtinCos16(args: List, position: Position, program: P throw SyntaxError("cos16 requires one argument", position) val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI - return LiteralValue(DataType.WORD, wordvalue = (32767.0* cos(rad)).toInt(), position = position) + return LiteralValue(DataType.WORD, wordvalue = (32767.0 * cos(rad)).toInt(), position = position) } private fun builtinCos16u(args: List, position: Position, program: Program): LiteralValue { @@ -434,7 +439,7 @@ private fun builtinCos16u(args: List, position: Position, program: throw SyntaxError("cos16u requires one argument", position) val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI - return LiteralValue(DataType.UWORD, wordvalue = (32768.0+32767.5* cos(rad)).toInt(), position = position) + return LiteralValue(DataType.UWORD, wordvalue = (32768.0 + 32767.5 * cos(rad)).toInt(), position = position) } private fun numericLiteral(value: Number, position: Position): LiteralValue { diff --git a/compiler/src/prog8/optimizing/CallGraph.kt b/compiler/src/prog8/optimizing/CallGraph.kt index 556283bb2..e229255ba 100644 --- a/compiler/src/prog8/optimizing/CallGraph.kt +++ b/compiler/src/prog8/optimizing/CallGraph.kt @@ -1,6 +1,13 @@ package prog8.optimizing import prog8.ast.* +import prog8.ast.base.ParentSentinel +import prog8.ast.base.VarDeclType +import prog8.ast.base.initvarsSubName +import prog8.ast.expressions.FunctionCall +import prog8.ast.expressions.IdentifierReference +import prog8.ast.processing.IAstProcessor +import prog8.ast.statements.* import prog8.compiler.loadAsmIncludeFile @@ -98,7 +105,7 @@ class CallGraph(private val program: Program): IAstProcessor { override fun process(subroutine: Subroutine): IStatement { if((subroutine.name=="start" && subroutine.definingScope().name=="main") - || subroutine.name==initvarsSubName || subroutine.definingModule().isLibraryModule) { + || subroutine.name== initvarsSubName || subroutine.definingModule().isLibraryModule) { // make sure the entrypoint is mentioned in the used symbols addNodeAndParentScopes(subroutine) } diff --git a/compiler/src/prog8/optimizing/ConstExprEvaluator.kt b/compiler/src/prog8/optimizing/ConstExprEvaluator.kt index 7d99a0313..52df3f12f 100644 --- a/compiler/src/prog8/optimizing/ConstExprEvaluator.kt +++ b/compiler/src/prog8/optimizing/ConstExprEvaluator.kt @@ -1,7 +1,11 @@ package prog8.optimizing import prog8.ast.* -import prog8.compiler.HeapValues +import prog8.ast.base.DataType +import prog8.ast.base.ExpressionError +import prog8.ast.base.FatalAstException +import prog8.ast.base.Position +import prog8.ast.expressions.LiteralValue import kotlin.math.pow @@ -40,7 +44,7 @@ class ConstExprEvaluator { if(left.asIntegerValue==null || amount.asIntegerValue==null) throw ExpressionError("cannot compute $left >> $amount", left.position) val result = - if(left.type==DataType.UBYTE || left.type==DataType.UWORD) + if(left.type== DataType.UBYTE || left.type== DataType.UWORD) left.asIntegerValue.ushr(amount.asIntegerValue) else left.asIntegerValue.shr(amount.asIntegerValue) diff --git a/compiler/src/prog8/optimizing/ConstantFolding.kt b/compiler/src/prog8/optimizing/ConstantFolding.kt index e19192319..926345753 100644 --- a/compiler/src/prog8/optimizing/ConstantFolding.kt +++ b/compiler/src/prog8/optimizing/ConstantFolding.kt @@ -1,7 +1,10 @@ package prog8.optimizing import prog8.ast.* -import prog8.compiler.CompilerException +import prog8.ast.base.* +import prog8.ast.expressions.* +import prog8.ast.processing.IAstProcessor +import prog8.ast.statements.* import prog8.compiler.HeapValues import prog8.compiler.IntegerOrAddressOf import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE @@ -75,19 +78,19 @@ class ConstantFolding(private val program: Program) : IAstProcessor { val eltType = rangeExpr.inferType(program)!! if(eltType in ByteDatatypes) { decl.value = LiteralValue(decl.datatype, - arrayvalue = constRange.map { LiteralValue(eltType, bytevalue=it.toShort(), position = decl.value!!.position ) } - .toTypedArray(), position=decl.value!!.position) + arrayvalue = constRange.map { LiteralValue(eltType, bytevalue = it.toShort(), position = decl.value!!.position) } + .toTypedArray(), position = decl.value!!.position) } else { decl.value = LiteralValue(decl.datatype, - arrayvalue = constRange.map { LiteralValue(eltType, wordvalue= it, position = decl.value!!.position ) } - .toTypedArray(), position=decl.value!!.position) + arrayvalue = constRange.map { LiteralValue(eltType, wordvalue = it, position = decl.value!!.position) } + .toTypedArray(), position = decl.value!!.position) } decl.value!!.linkParents(decl) optimizationsDone++ return decl } } - if(litval?.type==DataType.FLOAT) + if(litval?.type== DataType.FLOAT) errors.add(ExpressionError("arraysize requires only integers here", litval.position)) val size = decl.arraysize?.size() ?: return decl if ((litval==null || !litval.isArray) && rangeExpr==null) { @@ -96,24 +99,29 @@ class ConstantFolding(private val program: Program) : IAstProcessor { when(decl.datatype){ DataType.ARRAY_UB -> { if(fillvalue !in 0..255) - errors.add(ExpressionError("ubyte value overflow", litval?.position ?: decl.position)) + errors.add(ExpressionError("ubyte value overflow", litval?.position + ?: decl.position)) } DataType.ARRAY_B -> { if(fillvalue !in -128..127) - errors.add(ExpressionError("byte value overflow", litval?.position ?: decl.position)) + errors.add(ExpressionError("byte value overflow", litval?.position + ?: decl.position)) } DataType.ARRAY_UW -> { if(fillvalue !in 0..65535) - errors.add(ExpressionError("uword value overflow", litval?.position ?: decl.position)) + errors.add(ExpressionError("uword value overflow", litval?.position + ?: decl.position)) } DataType.ARRAY_W -> { if(fillvalue !in -32768..32767) - errors.add(ExpressionError("word value overflow", litval?.position ?: decl.position)) + errors.add(ExpressionError("word value overflow", litval?.position + ?: decl.position)) } else -> {} } val heapId = program.heap.addIntegerArray(decl.datatype, Array(size) { IntegerOrAddressOf(fillvalue, null) }) - decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval?.position ?: decl.position) + decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval?.position + ?: decl.position) optimizationsDone++ return decl } @@ -124,10 +132,12 @@ class ConstantFolding(private val program: Program) : IAstProcessor { // arraysize initializer is empty or a single int, and we know the size; create the arraysize. val fillvalue = if (litval == null) 0.0 else litval.asNumericValue?.toDouble() ?: 0.0 if(fillvalue< FLOAT_MAX_NEGATIVE || fillvalue> FLOAT_MAX_POSITIVE) - errors.add(ExpressionError("float value overflow", litval?.position ?: decl.position)) + errors.add(ExpressionError("float value overflow", litval?.position + ?: decl.position)) else { val heapId = program.heap.addDoublesArray(DoubleArray(size) { fillvalue }) - decl.value = LiteralValue(DataType.ARRAY_F, initHeapId = heapId, position = litval?.position ?: decl.position) + decl.value = LiteralValue(DataType.ARRAY_F, initHeapId = heapId, position = litval?.position + ?: decl.position) optimizationsDone++ return decl } @@ -154,7 +164,7 @@ class ConstantFolding(private val program: Program) : IAstProcessor { DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> { if(array.array!=null) { program.heap.update(heapId, HeapValues.HeapValue(decl.datatype, null, array.array, null)) - decl.value = LiteralValue(decl.datatype, initHeapId=heapId, position = litval.position) + decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval.position) } } DataType.ARRAY_F -> { @@ -385,7 +395,7 @@ class ConstantFolding(private val program: Program) : IAstProcessor { expr } else BinaryExpression( - BinaryExpression(expr.left, if(expr.operator=="-") "+" else "*", subExpr.right, subExpr.position), + BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position), expr.operator, subExpr.left, expr.position) } else { return if(subleftIsConst) { @@ -394,7 +404,7 @@ class ConstantFolding(private val program: Program) : IAstProcessor { } else BinaryExpression( subExpr.left, expr.operator, - BinaryExpression(expr.right, if(expr.operator=="-") "+" else "*", subExpr.right, subExpr.position), + BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position), expr.position) } } @@ -562,25 +572,25 @@ class ConstantFolding(private val program: Program) : IAstProcessor { val stepLiteral = iterableRange.step as? LiteralValue when(loopvar.datatype) { DataType.UBYTE -> { - if(rangeFrom.type!=DataType.UBYTE) { + if(rangeFrom.type!= DataType.UBYTE) { // attempt to translate the iterable into ubyte values resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange) } } DataType.BYTE -> { - if(rangeFrom.type!=DataType.BYTE) { + if(rangeFrom.type!= DataType.BYTE) { // attempt to translate the iterable into byte values resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange) } } DataType.UWORD -> { - if(rangeFrom.type!=DataType.UWORD) { + if(rangeFrom.type!= DataType.UWORD) { // attempt to translate the iterable into uword values resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange) } } DataType.WORD -> { - if(rangeFrom.type!=DataType.WORD) { + if(rangeFrom.type!= DataType.WORD) { // attempt to translate the iterable into word values resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange) } @@ -614,7 +624,7 @@ class ConstantFolding(private val program: Program) : IAstProcessor { val typesInArray = array.mapNotNull { it.inferType(program) }.toSet() val arrayDt = when { - array.any { it is AddressOf} -> DataType.ARRAY_UW + array.any { it is AddressOf } -> DataType.ARRAY_UW DataType.FLOAT in typesInArray -> DataType.ARRAY_F DataType.WORD in typesInArray -> DataType.ARRAY_W else -> { @@ -657,57 +667,57 @@ class ConstantFolding(private val program: Program) : IAstProcessor { when(assignment.singleTarget?.inferType(program, assignment)) { DataType.UWORD -> { // we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535, - if(lv.type==DataType.UBYTE) - assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position=lv.position) - else if(lv.type==DataType.BYTE && lv.bytevalue!!>=0) - assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position=lv.position) - else if(lv.type==DataType.WORD && lv.wordvalue!!>=0) - assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position=lv.position) - else if(lv.type==DataType.FLOAT) { + if(lv.type== DataType.UBYTE) + assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position = lv.position) + else if(lv.type== DataType.BYTE && lv.bytevalue!!>=0) + assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position = lv.position) + else if(lv.type== DataType.WORD && lv.wordvalue!!>=0) + assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position = lv.position) + else if(lv.type== DataType.FLOAT) { val d = lv.floatvalue!! if(floor(d)==d && d>=0 && d<=65535) - assignment.value = LiteralValue(DataType.UWORD, wordvalue=floor(d).toInt(), position=lv.position) + assignment.value = LiteralValue(DataType.UWORD, wordvalue = floor(d).toInt(), position = lv.position) } } DataType.UBYTE -> { // we can convert to UBYTE: UWORD <=255, BYTE >=0, FLOAT that's an integer 0..255, - if(lv.type==DataType.UWORD && lv.wordvalue!! <= 255) - assignment.value = LiteralValue(DataType.UBYTE, lv.wordvalue.toShort(), position=lv.position) - else if(lv.type==DataType.BYTE && lv.bytevalue!! >=0) - assignment.value = LiteralValue(DataType.UBYTE, lv.bytevalue.toShort(), position=lv.position) - else if(lv.type==DataType.FLOAT) { + if(lv.type== DataType.UWORD && lv.wordvalue!! <= 255) + assignment.value = LiteralValue(DataType.UBYTE, lv.wordvalue.toShort(), position = lv.position) + else if(lv.type== DataType.BYTE && lv.bytevalue!! >=0) + assignment.value = LiteralValue(DataType.UBYTE, lv.bytevalue.toShort(), position = lv.position) + else if(lv.type== DataType.FLOAT) { val d = lv.floatvalue!! if(floor(d)==d && d >=0 && d<=255) - assignment.value = LiteralValue(DataType.UBYTE, floor(d).toShort(), position=lv.position) + assignment.value = LiteralValue(DataType.UBYTE, floor(d).toShort(), position = lv.position) } } DataType.BYTE -> { // we can convert to BYTE: UWORD/UBYTE <= 127, FLOAT that's an integer 0..127 - if(lv.type==DataType.UWORD && lv.wordvalue!! <= 127) - assignment.value = LiteralValue(DataType.BYTE, lv.wordvalue.toShort(), position=lv.position) - else if(lv.type==DataType.UBYTE && lv.bytevalue!! <= 127) - assignment.value = LiteralValue(DataType.BYTE, lv.bytevalue, position=lv.position) - else if(lv.type==DataType.FLOAT) { + if(lv.type== DataType.UWORD && lv.wordvalue!! <= 127) + assignment.value = LiteralValue(DataType.BYTE, lv.wordvalue.toShort(), position = lv.position) + else if(lv.type== DataType.UBYTE && lv.bytevalue!! <= 127) + assignment.value = LiteralValue(DataType.BYTE, lv.bytevalue, position = lv.position) + else if(lv.type== DataType.FLOAT) { val d = lv.floatvalue!! if(floor(d)==d && d>=0 && d<=127) - assignment.value = LiteralValue(DataType.BYTE, floor(d).toShort(), position=lv.position) + assignment.value = LiteralValue(DataType.BYTE, floor(d).toShort(), position = lv.position) } } DataType.WORD -> { // we can convert to WORD: any UBYTE/BYTE, UWORD <= 32767, FLOAT that's an integer -32768..32767, - if(lv.type==DataType.UBYTE || lv.type==DataType.BYTE) - assignment.value = LiteralValue(DataType.WORD, wordvalue=lv.bytevalue!!.toInt(), position=lv.position) - else if(lv.type==DataType.UWORD && lv.wordvalue!! <= 32767) - assignment.value = LiteralValue(DataType.WORD, wordvalue=lv.wordvalue, position=lv.position) - else if(lv.type==DataType.FLOAT) { + if(lv.type== DataType.UBYTE || lv.type== DataType.BYTE) + assignment.value = LiteralValue(DataType.WORD, wordvalue = lv.bytevalue!!.toInt(), position = lv.position) + else if(lv.type== DataType.UWORD && lv.wordvalue!! <= 32767) + assignment.value = LiteralValue(DataType.WORD, wordvalue = lv.wordvalue, position = lv.position) + else if(lv.type== DataType.FLOAT) { val d = lv.floatvalue!! if(floor(d)==d && d>=-32768 && d<=32767) - assignment.value = LiteralValue(DataType.BYTE, floor(d).toShort(), position=lv.position) + assignment.value = LiteralValue(DataType.BYTE, floor(d).toShort(), position = lv.position) } } DataType.FLOAT -> { if(lv.isNumeric) - assignment.value = LiteralValue(DataType.FLOAT, floatvalue= lv.asNumericValue?.toDouble(), position=lv.position) + assignment.value = LiteralValue(DataType.FLOAT, floatvalue = lv.asNumericValue?.toDouble(), position = lv.position) } else -> {} } diff --git a/compiler/src/prog8/optimizing/Extensions.kt b/compiler/src/prog8/optimizing/Extensions.kt index a2ed77016..5dc952085 100644 --- a/compiler/src/prog8/optimizing/Extensions.kt +++ b/compiler/src/prog8/optimizing/Extensions.kt @@ -1,6 +1,8 @@ package prog8.optimizing import prog8.ast.* +import prog8.ast.base.AstException +import prog8.ast.statements.NopStatement import prog8.parser.ParsingFailedError diff --git a/compiler/src/prog8/optimizing/SimplifyExpressions.kt b/compiler/src/prog8/optimizing/SimplifyExpressions.kt index 1a1c83012..e38b74979 100644 --- a/compiler/src/prog8/optimizing/SimplifyExpressions.kt +++ b/compiler/src/prog8/optimizing/SimplifyExpressions.kt @@ -1,6 +1,13 @@ package prog8.optimizing import prog8.ast.* +import prog8.ast.base.AstException +import prog8.ast.base.DataType +import prog8.ast.base.IntegerDatatypes +import prog8.ast.base.NumericDatatypes +import prog8.ast.expressions.* +import prog8.ast.processing.IAstProcessor +import prog8.ast.statements.Assignment import kotlin.math.abs import kotlin.math.log2 @@ -336,48 +343,48 @@ internal class SimplifyExpressions(private val program: Program) : IAstProcessor DataType.UBYTE -> { if (targetDt == DataType.BYTE) { if(value.bytevalue!! < 127) - return Pair(true, LiteralValue(targetDt, value.bytevalue, position=value.position)) + return Pair(true, LiteralValue(targetDt, value.bytevalue, position = value.position)) } else if (targetDt == DataType.UWORD || targetDt == DataType.WORD) - return Pair(true, LiteralValue(targetDt, wordvalue = value.bytevalue!!.toInt(), position=value.position)) + return Pair(true, LiteralValue(targetDt, wordvalue = value.bytevalue!!.toInt(), position = value.position)) } DataType.BYTE -> { if (targetDt == DataType.UBYTE) { if(value.bytevalue!! >= 0) - return Pair(true, LiteralValue(targetDt, value.bytevalue, position=value.position)) + return Pair(true, LiteralValue(targetDt, value.bytevalue, position = value.position)) } else if (targetDt == DataType.UWORD) { if(value.bytevalue!! >= 0) - return Pair(true, LiteralValue(targetDt, wordvalue=value.bytevalue.toInt(), position=value.position)) + return Pair(true, LiteralValue(targetDt, wordvalue = value.bytevalue.toInt(), position = value.position)) } - else if (targetDt == DataType.WORD) return Pair(true, LiteralValue(targetDt, wordvalue=value.bytevalue!!.toInt(), position=value.position)) + else if (targetDt == DataType.WORD) return Pair(true, LiteralValue(targetDt, wordvalue = value.bytevalue!!.toInt(), position = value.position)) } DataType.UWORD -> { if (targetDt == DataType.UBYTE) { if(value.wordvalue!! <= 255) - return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position=value.position)) + return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position)) } else if (targetDt == DataType.BYTE) { if(value.wordvalue!! <= 127) - return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position=value.position)) + return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position)) } else if (targetDt == DataType.WORD) { if(value.wordvalue!! <= 32767) - return Pair(true, LiteralValue(targetDt, wordvalue=value.wordvalue, position=value.position)) + return Pair(true, LiteralValue(targetDt, wordvalue = value.wordvalue, position = value.position)) } } DataType.WORD -> { if (targetDt == DataType.UBYTE) { if(value.wordvalue!! in 0..255) - return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position=value.position)) + return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position)) } else if (targetDt == DataType.BYTE) { if(value.wordvalue!! in -128..127) - return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position=value.position)) + return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position)) } else if (targetDt == DataType.UWORD) { if(value.wordvalue!! >= 0) - return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position=value.position)) + return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position)) } } else -> {} diff --git a/compiler/src/prog8/optimizing/StatementOptimizer.kt b/compiler/src/prog8/optimizing/StatementOptimizer.kt index eafc7e222..f62885984 100644 --- a/compiler/src/prog8/optimizing/StatementOptimizer.kt +++ b/compiler/src/prog8/optimizing/StatementOptimizer.kt @@ -1,6 +1,10 @@ package prog8.optimizing import prog8.ast.* +import prog8.ast.base.* +import prog8.ast.expressions.* +import prog8.ast.processing.IAstProcessor +import prog8.ast.statements.* import prog8.compiler.target.c64.Petscii import prog8.functions.BuiltinFunctions import kotlin.math.floor @@ -419,7 +423,7 @@ internal class StatementOptimizer(private val program: Program, private val opti private fun hasContinueOrBreak(scope: INameScope): Boolean { - class Searcher:IAstProcessor + class Searcher: IAstProcessor { var count=0 @@ -481,7 +485,7 @@ internal class StatementOptimizer(private val program: Program, private val opti if(bexpr!=null) { val cv = bexpr.right.constValue(program)?.asNumericValue?.toDouble() if(cv==null) { - if(bexpr.operator=="+" && targetDt!=DataType.FLOAT) { + if(bexpr.operator=="+" && targetDt!= DataType.FLOAT) { if (bexpr.left isSameAs bexpr.right && target isSameAs bexpr.left) { bexpr.operator = "*" bexpr.right = LiteralValue.optimalInteger(2, assignment.value.position) diff --git a/compiler/src/prog8/parser/ModuleParsing.kt b/compiler/src/prog8/parser/ModuleParsing.kt index 5f6219445..84ecccb73 100644 --- a/compiler/src/prog8/parser/ModuleParsing.kt +++ b/compiler/src/prog8/parser/ModuleParsing.kt @@ -2,6 +2,12 @@ package prog8.parser import org.antlr.v4.runtime.* import prog8.ast.* +import prog8.ast.antlr.toAst +import prog8.ast.base.Position +import prog8.ast.base.SyntaxError +import prog8.ast.base.checkImportedValid +import prog8.ast.statements.Directive +import prog8.ast.statements.DirectiveArg import java.io.InputStream import java.nio.file.Files import java.nio.file.Path @@ -45,8 +51,8 @@ internal fun importModule(program: Program, filePath: Path): Module { internal fun importLibraryModule(program: Program, name: String): Module? { val import = Directive("%import", listOf( - DirectiveArg("", name, 42, position = Position("<<>>", 0, 0 ,0)) - ), Position("<<>>", 0, 0 ,0)) + DirectiveArg("", name, 42, position = Position("<<>>", 0, 0, 0)) + ), Position("<<>>", 0, 0, 0)) return executeImportDirective(program, import, Paths.get("")) } diff --git a/compiler/src/prog8/stackvm/Program.kt b/compiler/src/prog8/stackvm/Program.kt index a318ff5af..975dd0bde 100644 --- a/compiler/src/prog8/stackvm/Program.kt +++ b/compiler/src/prog8/stackvm/Program.kt @@ -1,6 +1,9 @@ package prog8.stackvm -import prog8.ast.* +import prog8.ast.antlr.unescape +import prog8.ast.base.* +import prog8.ast.expressions.AddressOf +import prog8.ast.expressions.IdentifierReference import prog8.compiler.RuntimeValue import prog8.compiler.HeapValues import prog8.compiler.IntegerOrAddressOf @@ -89,7 +92,7 @@ class Program (val name: String, } heapvalues.sortedBy { it.first }.forEach { when(it.second) { - DataType.STR, DataType.STR_S -> heap.addString(it.second, unescape(it.third.substring(1, it.third.length-1), Position("", 0, 0, 0))) + DataType.STR, DataType.STR_S -> heap.addString(it.second, unescape(it.third.substring(1, it.third.length - 1), Position("", 0, 0, 0))) DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> { val numbers = it.third.substring(1, it.third.length-1).split(',') @@ -98,8 +101,8 @@ class Program (val name: String, if(num.startsWith("&")) { // it's AddressOf val scopedname = num.substring(1) - val iref = IdentifierReference(scopedname.split('.'), Position("", 0,0,0)) - val addrOf = AddressOf(iref, Position("", 0,0,0)) + val iref = IdentifierReference(scopedname.split('.'), Position("", 0, 0, 0)) + val addrOf = AddressOf(iref, Position("", 0, 0, 0)) addrOf.scopedname=scopedname IntegerOrAddressOf(null, addrOf) } else { diff --git a/compiler/src/prog8/stackvm/StackVm.kt b/compiler/src/prog8/stackvm/StackVm.kt index b35fe0052..2af784110 100644 --- a/compiler/src/prog8/stackvm/StackVm.kt +++ b/compiler/src/prog8/stackvm/StackVm.kt @@ -1,6 +1,10 @@ package prog8.stackvm -import prog8.ast.* +import prog8.ast.base.DataType +import prog8.ast.base.IterableDatatypes +import prog8.ast.base.NumericDatatypes +import prog8.ast.base.Register +import prog8.ast.base.initvarsSubName import prog8.astvm.BitmapScreenPanel import prog8.astvm.Memory import prog8.compiler.RuntimeValue @@ -376,7 +380,7 @@ class StackVm(private var traceOutputFile: String?) { val value = evalstack.pop() checkDt(value, DataType.BYTE, DataType.UBYTE) val address = ins.arg!!.integerValue() - if(value.type==DataType.BYTE) + if(value.type== DataType.BYTE) mem.setSByte(address, value.integerValue().toShort()) else mem.setUByte(address, value.integerValue().toShort()) @@ -386,7 +390,7 @@ class StackVm(private var traceOutputFile: String?) { val value = evalstack.pop() checkDt(value, DataType.WORD, DataType.UWORD) val address = ins.arg!!.integerValue() - if(value.type==DataType.WORD) + if(value.type== DataType.WORD) mem.setSWord(address, value.integerValue()) else mem.setUWord(address, value.integerValue()) @@ -1673,7 +1677,7 @@ class StackVm(private var traceOutputFile: String?) { } else { // normal variable val variable = getVar(ins.callLabel!!) - if(variable.type==DataType.UWORD) { + if(variable.type== DataType.UWORD) { // assume the variable is a pointer (address) and get the word value from that memory location RuntimeValue(DataType.UWORD, mem.getUWord(variable.integerValue())) } else { @@ -1708,7 +1712,7 @@ class StackVm(private var traceOutputFile: String?) { if(ins.callLabel in memoryPointers) { val variable = memoryPointers.getValue(ins.callLabel!!) val address = variable.first + index*5 - if(variable.second==DataType.ARRAY_F) + if(variable.second== DataType.ARRAY_F) RuntimeValue(DataType.FLOAT, mem.getFloat(address)) else throw VmExecutionException("not a proper arraysize var with float elements") @@ -1737,13 +1741,13 @@ class StackVm(private var traceOutputFile: String?) { val memloc = memoryPointers[varname] if(memloc!=null) { // variable is the name of a pointer, write the byte value to that memory location - if(value.type==DataType.UBYTE) { - if(memloc.second!=DataType.ARRAY_UB) + if(value.type== DataType.UBYTE) { + if(memloc.second!= DataType.ARRAY_UB) throw VmExecutionException("invalid memory pointer type $memloc") mem.setUByte(memloc.first, value.integerValue().toShort()) } else { - if(memloc.second!=DataType.ARRAY_B) + if(memloc.second!= DataType.ARRAY_B) throw VmExecutionException("invalid memory pointer type $memloc") mem.setSByte(memloc.first, value.integerValue().toShort()) } @@ -1751,7 +1755,7 @@ class StackVm(private var traceOutputFile: String?) { val variable = getVar(varname) if (variable.type == DataType.UWORD) { // assume the variable is a pointer (address) and write the byte value to that memory location - if(value.type==DataType.UBYTE) + if(value.type== DataType.UBYTE) mem.setUByte(variable.integerValue(), value.integerValue().toShort()) else mem.setSByte(variable.integerValue(), value.integerValue().toShort()) @@ -1785,13 +1789,13 @@ class StackVm(private var traceOutputFile: String?) { val memloc = memoryPointers[varname] if(memloc!=null) { // variable is the name of a pointer, write the word value to that memory location - if(value.type==DataType.UWORD) { - if(memloc.second!=DataType.ARRAY_UW) + if(value.type== DataType.UWORD) { + if(memloc.second!= DataType.ARRAY_UW) throw VmExecutionException("invalid memory pointer type $memloc") mem.setUWord(memloc.first+index*2, value.integerValue()) } else { - if(memloc.second!=DataType.ARRAY_W) + if(memloc.second!= DataType.ARRAY_W) throw VmExecutionException("invalid memory pointer type $memloc") mem.setSWord(memloc.first+index*2, value.integerValue()) } @@ -1799,7 +1803,7 @@ class StackVm(private var traceOutputFile: String?) { val variable = getVar(varname) if (variable.type == DataType.UWORD) { // assume the variable is a pointer (address) and write the word value to that memory location - if(value.type==DataType.UWORD) + if(value.type== DataType.UWORD) mem.setUWord(variable.integerValue()+index*2, value.integerValue()) else mem.setSWord(variable.integerValue()+index*2, value.integerValue()) @@ -1824,7 +1828,7 @@ class StackVm(private var traceOutputFile: String?) { val memloc = memoryPointers[varname] if(memloc!=null) { // variable is the name of a pointer, write the float value to that memory location - if(memloc.second!=DataType.ARRAY_F) + if(memloc.second!= DataType.ARRAY_F) throw VmExecutionException("invalid memory pointer type $memloc") mem.setFloat(memloc.first+index*5, value.numericValue().toDouble()) } else { @@ -2268,9 +2272,9 @@ class StackVm(private var traceOutputFile: String?) { val numbytes = evalstack.pop().integerValue() val bytevalue = value.integerValue().toShort() when { - value.type==DataType.UBYTE -> for(addr in address until address+numbytes) + value.type== DataType.UBYTE -> for(addr in address until address+numbytes) mem.setUByte(addr, bytevalue) - value.type==DataType.BYTE -> for(addr in address until address+numbytes) + value.type== DataType.BYTE -> for(addr in address until address+numbytes) mem.setSByte(addr, bytevalue) else -> throw VmExecutionException("(u)byte value expected") } @@ -2281,9 +2285,9 @@ class StackVm(private var traceOutputFile: String?) { val numwords = evalstack.pop().integerValue() val wordvalue = value.integerValue() when { - value.type==DataType.UWORD -> for(addr in address until address+numwords*2 step 2) + value.type== DataType.UWORD -> for(addr in address until address+numwords*2 step 2) mem.setUWord(addr, wordvalue) - value.type==DataType.WORD -> for(addr in address until address+numwords*2 step 2) + value.type== DataType.WORD -> for(addr in address until address+numwords*2 step 2) mem.setSWord(addr, wordvalue) else -> throw VmExecutionException("(u)word value expected") } diff --git a/compiler/test/LiteralValueTests.kt b/compiler/test/LiteralValueTests.kt index 93f5a14dc..b0ff0c6c4 100644 --- a/compiler/test/LiteralValueTests.kt +++ b/compiler/test/LiteralValueTests.kt @@ -2,9 +2,9 @@ package prog8tests import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance -import prog8.ast.DataType -import prog8.ast.LiteralValue -import prog8.ast.Position +import prog8.ast.base.DataType +import prog8.ast.expressions.LiteralValue +import prog8.ast.base.Position import kotlin.test.* @@ -16,7 +16,7 @@ private fun sameValueAndType(lv1: LiteralValue, lv2: LiteralValue): Boolean { @TestInstance(TestInstance.Lifecycle.PER_CLASS) class TestParserLiteralValue { - private val dummyPos = Position("test", 0,0,0) + private val dummyPos = Position("test", 0, 0, 0) @Test fun testIdentity() { @@ -33,99 +33,99 @@ class TestParserLiteralValue { @Test fun testEqualsAndNotEquals() { - assertEquals(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.UBYTE, 100, position=dummyPos)) - assertEquals(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=100, position=dummyPos)) - assertEquals(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos)) - assertEquals(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos), LiteralValue(DataType.UBYTE, 254, position=dummyPos)) - assertEquals(LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos)) - assertEquals(LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=12345.0, position=dummyPos)) - assertEquals(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos), LiteralValue(DataType.UBYTE, 100, position=dummyPos)) - assertEquals(LiteralValue(DataType.FLOAT, floatvalue=22239.0, position=dummyPos), LiteralValue(DataType.UWORD,wordvalue=22239, position=dummyPos)) - assertEquals(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos)) + assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos)) + assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 100, position = dummyPos)) + assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos)) + assertEquals(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos), LiteralValue(DataType.UBYTE, 254, position = dummyPos)) + assertEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos)) + assertEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12345.0, position = dummyPos)) + assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos)) + assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 22239.0, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 22239, position = dummyPos)) + assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos)) - assertTrue(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.UBYTE, 100, position=dummyPos))) - assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=100, position=dummyPos))) - assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos))) - assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos), LiteralValue(DataType.UBYTE, 254, position=dummyPos))) - assertTrue(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos))) - assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=12345.0, position=dummyPos))) - assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos), LiteralValue(DataType.UBYTE, 100, position=dummyPos))) - assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue=22239.0, position=dummyPos), LiteralValue(DataType.UWORD,wordvalue=22239, position=dummyPos))) - assertTrue(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos))) + assertTrue(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos))) + assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 100, position = dummyPos))) + assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))) + assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos), LiteralValue(DataType.UBYTE, 254, position = dummyPos))) + assertTrue(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos))) + assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12345.0, position = dummyPos))) + assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos))) + assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 22239.0, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 22239, position = dummyPos))) + assertTrue(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos))) - assertNotEquals(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.UBYTE, 101, position=dummyPos)) - assertNotEquals(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=101, position=dummyPos)) - assertNotEquals(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=101.0, position=dummyPos)) - assertNotEquals(LiteralValue(DataType.UWORD, wordvalue=245, position=dummyPos), LiteralValue(DataType.UBYTE, 246, position=dummyPos)) - assertNotEquals(LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=12346, position=dummyPos)) - assertNotEquals(LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=12346.0, position=dummyPos)) - assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.UBYTE, 9, position=dummyPos)) - assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=9, position=dummyPos)) - assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=9.0, position=dummyPos)) + assertNotEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 101, position = dummyPos)) + assertNotEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 101, position = dummyPos)) + assertNotEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 101.0, position = dummyPos)) + assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 245, position = dummyPos), LiteralValue(DataType.UBYTE, 246, position = dummyPos)) + assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12346, position = dummyPos)) + assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12346.0, position = dummyPos)) + assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UBYTE, 9, position = dummyPos)) + assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 9, position = dummyPos)) + assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.0, position = dummyPos)) - assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.UBYTE, 101, position=dummyPos))) - assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=101, position=dummyPos))) - assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=101.0, position=dummyPos))) - assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue=245, position=dummyPos), LiteralValue(DataType.UBYTE, 246, position=dummyPos))) - assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=12346, position=dummyPos))) - assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue=12345, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=12346.0, position=dummyPos))) - assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.UBYTE, 9, position=dummyPos))) - assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.UWORD, wordvalue=9, position=dummyPos))) - assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue=9.99, position=dummyPos), LiteralValue(DataType.FLOAT, floatvalue=9.0, position=dummyPos))) + assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 101, position = dummyPos))) + assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 101, position = dummyPos))) + assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 101.0, position = dummyPos))) + assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 245, position = dummyPos), LiteralValue(DataType.UBYTE, 246, position = dummyPos))) + assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12346, position = dummyPos))) + assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12346.0, position = dummyPos))) + assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UBYTE, 9, position = dummyPos))) + assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 9, position = dummyPos))) + assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.0, position = dummyPos))) - assertTrue(sameValueAndType(LiteralValue(DataType.STR, strvalue = "hello", position=dummyPos), LiteralValue(DataType.STR, strvalue="hello", position=dummyPos))) - assertFalse(sameValueAndType(LiteralValue(DataType.STR, strvalue = "hello", position=dummyPos), LiteralValue(DataType.STR, strvalue="bye", position=dummyPos))) + assertTrue(sameValueAndType(LiteralValue(DataType.STR, strvalue = "hello", position = dummyPos), LiteralValue(DataType.STR, strvalue = "hello", position = dummyPos))) + assertFalse(sameValueAndType(LiteralValue(DataType.STR, strvalue = "hello", position = dummyPos), LiteralValue(DataType.STR, strvalue = "bye", position = dummyPos))) - val lvOne = LiteralValue(DataType.UBYTE, 1, position=dummyPos) - val lvTwo = LiteralValue(DataType.UBYTE, 2, position=dummyPos) - val lvThree = LiteralValue(DataType.UBYTE, 3, position=dummyPos) - val lvOneR = LiteralValue(DataType.UBYTE, 1, position=dummyPos) - val lvTwoR = LiteralValue(DataType.UBYTE, 2, position=dummyPos) - val lvThreeR = LiteralValue(DataType.UBYTE, 3, position=dummyPos) - val lvFour= LiteralValue(DataType.UBYTE, 4, position=dummyPos) - val lv1 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOne, lvTwo, lvThree), position=dummyPos) - val lv2 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOneR, lvTwoR, lvThreeR), position=dummyPos) - val lv3 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOneR, lvTwoR, lvFour), position=dummyPos) + val lvOne = LiteralValue(DataType.UBYTE, 1, position = dummyPos) + val lvTwo = LiteralValue(DataType.UBYTE, 2, position = dummyPos) + val lvThree = LiteralValue(DataType.UBYTE, 3, position = dummyPos) + val lvOneR = LiteralValue(DataType.UBYTE, 1, position = dummyPos) + val lvTwoR = LiteralValue(DataType.UBYTE, 2, position = dummyPos) + val lvThreeR = LiteralValue(DataType.UBYTE, 3, position = dummyPos) + val lvFour= LiteralValue(DataType.UBYTE, 4, position = dummyPos) + val lv1 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOne, lvTwo, lvThree), position = dummyPos) + val lv2 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOneR, lvTwoR, lvThreeR), position = dummyPos) + val lv3 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOneR, lvTwoR, lvFour), position = dummyPos) assertEquals(lv1, lv2) assertNotEquals(lv1, lv3) } @Test fun testGreaterThan(){ - assertTrue(LiteralValue(DataType.UBYTE, 100, position=dummyPos) > LiteralValue(DataType.UBYTE, 99, position=dummyPos)) - assertTrue(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos) > LiteralValue(DataType.UWORD, wordvalue=253, position=dummyPos)) - assertTrue(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) > LiteralValue(DataType.FLOAT, floatvalue=99.9, position=dummyPos)) + assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) > LiteralValue(DataType.UBYTE, 99, position = dummyPos)) + assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) > LiteralValue(DataType.UWORD, wordvalue = 253, position = dummyPos)) + assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) > LiteralValue(DataType.FLOAT, floatvalue = 99.9, position = dummyPos)) - assertTrue(LiteralValue(DataType.UBYTE, 100, position=dummyPos) >= LiteralValue(DataType.UBYTE, 100, position=dummyPos)) - assertTrue(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos) >= LiteralValue(DataType.UWORD,wordvalue= 254, position=dummyPos)) - assertTrue(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos)) + assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) >= LiteralValue(DataType.UBYTE, 100, position = dummyPos)) + assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) >= LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos)) + assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos)) - assertFalse(LiteralValue(DataType.UBYTE, 100, position=dummyPos) > LiteralValue(DataType.UBYTE, 100, position=dummyPos)) - assertFalse(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos) > LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos)) - assertFalse(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) > LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos)) + assertFalse(LiteralValue(DataType.UBYTE, 100, position = dummyPos) > LiteralValue(DataType.UBYTE, 100, position = dummyPos)) + assertFalse(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) > LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos)) + assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) > LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos)) - assertFalse(LiteralValue(DataType.UBYTE, 100, position=dummyPos) >= LiteralValue(DataType.UBYTE, 101, position=dummyPos)) - assertFalse(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos) >= LiteralValue(DataType.UWORD,wordvalue= 255, position=dummyPos)) - assertFalse(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue=100.1, position=dummyPos)) + assertFalse(LiteralValue(DataType.UBYTE, 100, position = dummyPos) >= LiteralValue(DataType.UBYTE, 101, position = dummyPos)) + assertFalse(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) >= LiteralValue(DataType.UWORD, wordvalue = 255, position = dummyPos)) + assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue = 100.1, position = dummyPos)) } @Test fun testLessThan() { - assertTrue(LiteralValue(DataType.UBYTE, 100, position=dummyPos) < LiteralValue(DataType.UBYTE, 101, position=dummyPos)) - assertTrue(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos) < LiteralValue(DataType.UWORD, wordvalue=255, position=dummyPos)) - assertTrue(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) < LiteralValue(DataType.FLOAT, floatvalue=100.1, position=dummyPos)) + assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) < LiteralValue(DataType.UBYTE, 101, position = dummyPos)) + assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) < LiteralValue(DataType.UWORD, wordvalue = 255, position = dummyPos)) + assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) < LiteralValue(DataType.FLOAT, floatvalue = 100.1, position = dummyPos)) - assertTrue(LiteralValue(DataType.UBYTE, 100, position=dummyPos) <= LiteralValue(DataType.UBYTE, 100, position=dummyPos)) - assertTrue(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos) <= LiteralValue(DataType.UWORD,wordvalue= 254, position=dummyPos)) - assertTrue(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos)) + assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) <= LiteralValue(DataType.UBYTE, 100, position = dummyPos)) + assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) <= LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos)) + assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos)) - assertFalse(LiteralValue(DataType.UBYTE, 100, position=dummyPos) < LiteralValue(DataType.UBYTE, 100, position=dummyPos)) - assertFalse(LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos) < LiteralValue(DataType.UWORD, wordvalue=254, position=dummyPos)) - assertFalse(LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos) < LiteralValue(DataType.FLOAT, floatvalue=100.0, position=dummyPos)) + assertFalse(LiteralValue(DataType.UBYTE, 100, position = dummyPos) < LiteralValue(DataType.UBYTE, 100, position = dummyPos)) + assertFalse(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) < LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos)) + assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) < LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos)) - assertFalse(LiteralValue(DataType.UBYTE, 100, position=dummyPos) <= LiteralValue(DataType.UBYTE, 99, position=dummyPos)) - assertFalse(LiteralValue(DataType.UWORD,wordvalue= 254, position=dummyPos) <= LiteralValue(DataType.UWORD,wordvalue= 253, position=dummyPos)) - assertFalse(LiteralValue(DataType.FLOAT,floatvalue= 100.0, position=dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue=99.9, position=dummyPos)) + assertFalse(LiteralValue(DataType.UBYTE, 100, position = dummyPos) <= LiteralValue(DataType.UBYTE, 99, position = dummyPos)) + assertFalse(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) <= LiteralValue(DataType.UWORD, wordvalue = 253, position = dummyPos)) + assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue = 99.9, position = dummyPos)) } } diff --git a/compiler/test/RuntimeValueTests.kt b/compiler/test/RuntimeValueTests.kt index c960a0e3b..0aed3a1c7 100644 --- a/compiler/test/RuntimeValueTests.kt +++ b/compiler/test/RuntimeValueTests.kt @@ -2,7 +2,7 @@ package prog8tests import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance -import prog8.ast.DataType +import prog8.ast.base.DataType import prog8.compiler.RuntimeValue import kotlin.test.* diff --git a/compiler/test/StackVMOpcodeTests.kt b/compiler/test/StackVMOpcodeTests.kt index fd05b281f..c17cb44d1 100644 --- a/compiler/test/StackVMOpcodeTests.kt +++ b/compiler/test/StackVMOpcodeTests.kt @@ -4,10 +4,10 @@ import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.empty import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance -import prog8.ast.ByteDatatypes -import prog8.ast.DataType -import prog8.ast.IterableDatatypes -import prog8.ast.WordDatatypes +import prog8.ast.base.ByteDatatypes +import prog8.ast.base.DataType +import prog8.ast.base.IterableDatatypes +import prog8.ast.base.WordDatatypes import prog8.compiler.RuntimeValue import prog8.compiler.HeapValues import prog8.compiler.intermediate.Instruction diff --git a/compiler/test/UnitTests.kt b/compiler/test/UnitTests.kt index 9a45d3daa..4c1479a33 100644 --- a/compiler/test/UnitTests.kt +++ b/compiler/test/UnitTests.kt @@ -5,7 +5,9 @@ import org.hamcrest.Matchers.closeTo import org.hamcrest.Matchers.equalTo import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance -import prog8.ast.* +import prog8.ast.base.DataType +import prog8.ast.base.Position +import prog8.ast.expressions.LiteralValue import prog8.compiler.RuntimeValue import prog8.compiler.* import prog8.compiler.target.c64.* @@ -334,8 +336,8 @@ class TestPetscii { @Test fun testLiteralValueComparisons() { - val ten = LiteralValue(DataType.UWORD, wordvalue=10, position=Position("", 0 ,0 ,0)) - val nine = LiteralValue(DataType.UBYTE, bytevalue=9, position=Position("", 0 ,0 ,0)) + val ten = LiteralValue(DataType.UWORD, wordvalue = 10, position = Position("", 0, 0, 0)) + val nine = LiteralValue(DataType.UBYTE, bytevalue = 9, position = Position("", 0, 0, 0)) assertEquals(ten, ten) assertNotEquals(ten, nine) assertFalse(ten != ten) @@ -351,8 +353,8 @@ class TestPetscii { assertTrue(ten <= ten) assertFalse(ten < ten) - val abc = LiteralValue(DataType.STR, strvalue = "abc", position=Position("", 0 ,0 ,0)) - val abd = LiteralValue(DataType.STR, strvalue = "abd", position=Position("", 0 ,0 ,0)) + val abc = LiteralValue(DataType.STR, strvalue = "abc", position = Position("", 0, 0, 0)) + val abd = LiteralValue(DataType.STR, strvalue = "abd", position = Position("", 0, 0, 0)) assertEquals(abc, abc) assertTrue(abc!=abd) assertFalse(abc!=abc)