diff --git a/compiler/src/prog8/ast/base/Extensions.kt b/compiler/src/prog8/ast/base/Extensions.kt index 97f629d0a..57cb03056 100644 --- a/compiler/src/prog8/ast/base/Extensions.kt +++ b/compiler/src/prog8/ast/base/Extensions.kt @@ -61,8 +61,9 @@ internal fun Program.checkIdentifiers(errors: ErrorReporter) { checker2.visit(this) if(errors.isEmpty()) { - val checker = AstIdentifierTransforms(this) - checker.visit(this) + val transforms = AstVariousTransforms(this) + transforms.visit(this) + transforms.applyModifications() } if (modules.map { it.name }.toSet().size != modules.size) { diff --git a/compiler/src/prog8/ast/processing/AstIdentifierTransforms.kt b/compiler/src/prog8/ast/processing/AstIdentifierTransforms.kt deleted file mode 100644 index 06032e8e6..000000000 --- a/compiler/src/prog8/ast/processing/AstIdentifierTransforms.kt +++ /dev/null @@ -1,212 +0,0 @@ -package prog8.ast.processing - -import prog8.ast.INameScope -import prog8.ast.Module -import prog8.ast.Node -import prog8.ast.Program -import prog8.ast.base.* -import prog8.ast.expressions.* -import prog8.ast.statements.* - - -// TODO implement using AstWalker instead of IAstModifyingVisitor -internal class AstIdentifierTransforms(private val program: Program) : IAstModifyingVisitor { - private val vardeclsToAdd = mutableMapOf>() - - override fun visit(module: Module) { - vardeclsToAdd.clear() - super.visit(module) - // add any new vardecls to the various scopes - for((where, decls) in vardeclsToAdd) { - where.statements.addAll(0, decls) - decls.forEach { it.linkParents(where as Node) } - } - } - - override fun visit(functionCall: FunctionCall): Expression { - // TODO: modification below: - if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") { - // lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte" - val typecast = TypecastExpression(functionCall.args.single(), DataType.UBYTE, false, functionCall.position) - typecast.linkParents(functionCall.parent) - return super.visit(typecast) - } - return super.visit(functionCall) - } - - override fun visit(decl: VarDecl): Statement { - - // is it a struct variable? then define all its struct members as mangled names, - // and include the original decl as well. - if(decl.datatype==DataType.STRUCT) { - // TODO modification below: - val decls = decl.flattenStructMembers() - decls.add(decl) - val result = AnonymousScope(decls, decl.position) - result.linkParents(decl.parent) - return result - } - - return super.visit(decl) - } - - override fun visit(subroutine: Subroutine): Statement { - // TODO modification below: - - // inject subroutine params as local variables (if they're not there yet) (for non-kernel subroutines and non-asm parameters) - // NOTE: - // - numeric types BYTE and WORD and FLOAT are passed by value; - // - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter) - val symbolsInSub = subroutine.allDefinedSymbols() - val namesInSub = symbolsInSub.map{ it.first }.toSet() - if(subroutine.asmAddress==null) { - if(subroutine.asmParameterRegisters.isEmpty()) { - subroutine.parameters - .filter { it.name !in namesInSub } - .forEach { - val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position) - vardecl.linkParents(subroutine) - subroutine.statements.add(0, vardecl) - } - } - } - return super.visit(subroutine) - } - - override fun visit(forLoop: ForLoop): Statement { - // If the for loop has a decltype, it means to declare the loopvar inside the loop body - // rather than reusing an already declared loopvar from an outer scope. - // For loops that loop over an interable variable (instead of a range of numbers) get an - // additional interation count variable in their scope. - // TODO modifications below: - val loopVar = forLoop.loopVar - if (loopVar != null) { - val validName = forLoop.body.name.replace("<", "").replace(">", "").replace("-", "") - val loopvarName = "prog8_loopvar_$validName" - if (forLoop.iterable !is RangeExpr) { - val existing = if (forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(loopvarName), forLoop.body.statements.first()) - if (existing == null) { - // create loop iteration counter variable (without value, to avoid an assignment) - val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE, null, loopvarName, null, null, - isArray = false, autogeneratedDontRemove = true, position = loopVar.position) - vardecl.linkParents(forLoop.body) - forLoop.body.statements.add(0, vardecl) - loopVar.parent = forLoop.body // loopvar 'is defined in the body' - } - } - } - return super.visit(forLoop) - } - - override fun visit(arrayLiteral: ArrayLiteralValue): Expression { - // TODO modifications below: - val array = super.visit(arrayLiteral) - if(array is ArrayLiteralValue) { - val vardecl = array.parent as? VarDecl - // adjust the datatype of the array (to an educated guess) - if(vardecl!=null) { - val arrayDt = array.type - if(!arrayDt.istype(vardecl.datatype)) { - val cast = array.cast(vardecl.datatype) - if (cast != null) { - vardecl.value = cast - cast.linkParents(vardecl) - return cast - } - } - return array - } - else { - val arrayDt = array.guessDatatype(program) - if(arrayDt.isKnown) { - // this array literal is part of an expression, turn it into an identifier reference - val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT)) - return if (litval2 != null) { - litval2.parent = array.parent - makeIdentifierFromRefLv(litval2) - } else array - } - } - } - return array - } - - override fun visit(stringLiteral: StringLiteralValue): Expression { - // todo modification below: - val string = super.visit(stringLiteral) - if(string is StringLiteralValue) { - val vardecl = string.parent as? VarDecl - // intern the string; move it into the heap - return if (vardecl != null) - string - else - makeIdentifierFromRefLv(string) // replace the literal string by a identifier reference. - } - return string - } - - override fun visit(expr: BinaryExpression): Expression { - return when { - expr.left is StringLiteralValue -> - processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr) - expr.right is StringLiteralValue -> - processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr) - else -> super.visit(expr) - } - } - - private fun makeIdentifierFromRefLv(array: ArrayLiteralValue): IdentifierReference { - // a referencetype literal value that's not declared as a variable - // we need to introduce an auto-generated variable for this to be able to refer to the value - // note: if the var references the same literal value, it is not yet de-duplicated here. - val scope = array.definingScope() - val variable = VarDecl.createAuto(array) - return replaceWithIdentifier(variable, scope, array.parent) - } - - private fun makeIdentifierFromRefLv(string: StringLiteralValue): IdentifierReference { - // a referencetype literal value that's not declared as a variable - // we need to introduce an auto-generated variable for this to be able to refer to the value - // note: if the var references the same literal value, it is not yet de-duplicated here. - val scope = string.definingScope() - val variable = VarDecl.createAuto(string) - return replaceWithIdentifier(variable, scope, string.parent) - } - - private fun replaceWithIdentifier(variable: VarDecl, scope: INameScope, parent: Node): IdentifierReference { - val variable1 = addVarDecl(scope, variable) - // replace the reference literal by a identifier reference - val identifier = IdentifierReference(listOf(variable1.name), variable1.position) - identifier.parent = parent - return identifier - } - - private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression { - val constvalue = operand.constValue(program) - if(constvalue!=null) { - if (expr.operator == "*") { - // repeat a string a number of times - return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position) - } - } - if(expr.operator == "+" && operand is StringLiteralValue) { - // concatenate two strings - return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position) - } - return expr - } - - private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl { - if(scope !in vardeclsToAdd) - vardeclsToAdd[scope] = mutableListOf() - val declList = vardeclsToAdd.getValue(scope) - val existing = declList.singleOrNull { it.name==variable.name } - return if(existing!=null) { - existing - } else { - declList.add(variable) - variable - } - } - -} diff --git a/compiler/src/prog8/ast/processing/AstVariousTransforms.kt b/compiler/src/prog8/ast/processing/AstVariousTransforms.kt new file mode 100644 index 000000000..adc2792a9 --- /dev/null +++ b/compiler/src/prog8/ast/processing/AstVariousTransforms.kt @@ -0,0 +1,132 @@ +package prog8.ast.processing + +import prog8.ast.Node +import prog8.ast.Program +import prog8.ast.base.* +import prog8.ast.expressions.* +import prog8.ast.statements.* + + +internal class AstVariousTransforms(private val program: Program) : AstWalker() { + + override fun before(functionCall: FunctionCall, parent: Node): Iterable { + if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") { + // lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte" + val typecast = TypecastExpression(functionCall.args.single(), DataType.UBYTE, false, functionCall.position) + return listOf(IAstModification.ReplaceNode( + functionCall, typecast, parent + )) + } + + return emptyList() + } + + override fun before(decl: VarDecl, parent: Node): Iterable { + // is it a struct variable? then define all its struct members as mangled names, + // and include the original decl as well. + if(decl.datatype==DataType.STRUCT && !decl.structHasBeenFlattened) { + val decls = decl.flattenStructMembers() + decls.add(decl) + val result = AnonymousScope(decls, decl.position) + return listOf(IAstModification.ReplaceNode( + decl, result, parent + )) + } + + return emptyList() + } + + override fun after(subroutine: Subroutine, parent: Node): Iterable { + // For non-kernel subroutines and non-asm parameters: + // inject subroutine params as local variables (if they're not there yet). + val symbolsInSub = subroutine.allDefinedSymbols() + val namesInSub = symbolsInSub.map{ it.first }.toSet() + if(subroutine.asmAddress==null) { + if(subroutine.asmParameterRegisters.isEmpty()) { + return subroutine.parameters + .filter { it.name !in namesInSub } + .map { + val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position) + IAstModification.InsertFirst(vardecl, subroutine) + } + } + } + + return emptyList() + } + + override fun before(expr: BinaryExpression, parent: Node): Iterable { + when { + expr.left is StringLiteralValue -> + return listOf(IAstModification.ReplaceNode( + expr, + processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr), + parent + )) + expr.right is StringLiteralValue -> + return listOf(IAstModification.ReplaceNode( + expr, + processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr), + parent + )) + } + + return emptyList() + } + + override fun after(string: StringLiteralValue, parent: Node): Iterable { + if(string.parent !is VarDecl) { + // replace the literal string by a identifier reference to a new local vardecl + val vardecl = VarDecl.createAuto(string) + val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position) + return listOf( + IAstModification.ReplaceNode(string, identifier, parent), + IAstModification.InsertFirst(vardecl, string.definingScope() as Node) + ) + } + return emptyList() + } + + override fun after(array: ArrayLiteralValue, parent: Node): Iterable { + val vardecl = array.parent as? VarDecl + if(vardecl!=null) { + // adjust the datatype of the array (to an educated guess) + val arrayDt = array.type + if(!arrayDt.istype(vardecl.datatype)) { + val cast = array.cast(vardecl.datatype) + if (cast != null) + return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl)) + } + } else { + val arrayDt = array.guessDatatype(program) + if(arrayDt.isKnown) { + // this array literal is part of an expression, turn it into an identifier reference + val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT)) + if(litval2!=null) { + val vardecl = VarDecl.createAuto(litval2) + val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position) + return listOf( + IAstModification.ReplaceNode(array, identifier, parent), + IAstModification.InsertFirst(vardecl, array.definingScope() as Node) + ) + } + } + } + return emptyList() + } + + private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression { + val constvalue = operand.constValue(program) + if(constvalue!=null) { + if (expr.operator == "*") { + // repeat a string a number of times + return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position) + } + } + if(expr.operator == "+" && operand is StringLiteralValue) { + // concatenate two strings + return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position) + } + return expr + } +} diff --git a/compiler/src/prog8/compiler/target/c64/codegen/ExpressionsAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/ExpressionsAsmGen.kt index 0895c7f04..fd7c5f359 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/ExpressionsAsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/ExpressionsAsmGen.kt @@ -28,7 +28,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge is RegisterExpr -> translateExpression(expression) is IdentifierReference -> translateExpression(expression) is FunctionCall -> translateExpression(expression) - is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array assignment") + is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable") is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened") is RangeExpr -> throw AssemblyError("range expression should have been changed into array values") } diff --git a/compiler/src/prog8/compiler/target/c64/codegen/ForLoopsAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/ForLoopsAsmGen.kt index eb9765715..7b4e4b0a8 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/ForLoopsAsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/ForLoopsAsmGen.kt @@ -37,7 +37,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen: is IdentifierReference -> { translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference) } - else -> throw AssemblyError("can't iterate over ${stmt.iterable}") + else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable") } } diff --git a/compiler/src/prog8/optimizer/ConstantFoldingOptimizer.kt b/compiler/src/prog8/optimizer/ConstantFoldingOptimizer.kt index 9ef66603f..9db62ceeb 100644 --- a/compiler/src/prog8/optimizer/ConstantFoldingOptimizer.kt +++ b/compiler/src/prog8/optimizer/ConstantFoldingOptimizer.kt @@ -291,10 +291,6 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va override fun visit(expr: BinaryExpression): Expression { super.visit(expr) - if(expr.left is StringLiteralValue || expr.left is ArrayLiteralValue - || expr.right is StringLiteralValue || expr.right is ArrayLiteralValue) - throw FatalAstException("binexpr with reference litval instead of numeric") - val leftconst = expr.left.constValue(program) val rightconst = expr.right.constValue(program)