diff --git a/codeCore/src/prog8/code/ast/AstStatements.kt b/codeCore/src/prog8/code/ast/AstStatements.kt index edb8d8054..558c1e6a8 100644 --- a/codeCore/src/prog8/code/ast/AstStatements.kt +++ b/codeCore/src/prog8/code/ast/AstStatements.kt @@ -41,9 +41,18 @@ class PtSubroutineParameter(name: String, val type: DataType, position: Position sealed interface IPtAssignment { val children: MutableList val target: PtAssignTarget - get() = children[0] as PtAssignTarget + get() { + if(children.size==2) + return children[0] as PtAssignTarget + else if(children.size<2) + throw AssemblyError("incomplete node") + else + throw AssemblyError("no singular target") + } val value: PtExpression - get() = children[1] as PtExpression + get() = children.last() as PtExpression + val multiTarget: Boolean + get() = children.size>2 } class PtAssignment(position: Position) : PtNode(position), IPtAssignment diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt index 9c8bbe908..521e2746b 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt @@ -1,5 +1,6 @@ package prog8.codegen.intermediate +import prog8.code.StRomSub import prog8.code.ast.* import prog8.code.core.* import prog8.intermediate.* @@ -8,15 +9,34 @@ import prog8.intermediate.* internal class AssignmentGen(private val codeGen: IRCodeGen, private val expressionEval: ExpressionGen) { internal fun translate(assignment: PtAssignment): IRCodeChunks { - if(assignment.target.children.single() is PtIrRegister) - throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister") + if(assignment.multiTarget) { + val values = assignment.value as? PtFunctionCall + ?: throw AssemblyError("only function calls can return multiple values in a multi-assign") - val chunks = translateRegularAssign(assignment) - chunks.filterIsInstance().firstOrNull()?.appendSrcPosition(assignment.position) - return chunks + val sub = codeGen.symbolTable.lookup(values.name) as? StRomSub + ?: throw AssemblyError("only asmsubs can return multiple values") + + val result = mutableListOf() + sub.returns.zip(assignment.children).forEach { (returns, target) -> + val singleAssign = PtAssignment(assignment.position) + singleAssign.children.add(target) + TODO("IR cannot store machine register results yet ${assignment.position}") + // singleAssign.children.add(PtMachineRegister(4242, returns.type, assignment.position)) + // result += translateRegularAssign(singleAssign) + } + return result + } else { + if (assignment.target.children.single() is PtIrRegister) + throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister") + + val chunks = translateRegularAssign(assignment) + chunks.filterIsInstance().firstOrNull()?.appendSrcPosition(assignment.position) + return chunks + } } internal fun translate(augAssign: PtAugmentedAssign): IRCodeChunks { + // augmented assignment always has just a single target if(augAssign.target.children.single() is PtIrRegister) throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister") diff --git a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt index cbfb3ea1f..fad9cbcce 100644 --- a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt +++ b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt @@ -309,7 +309,7 @@ internal class ConstantIdentifierReplacer( val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position) return if(arrayIdx.parent is AssignTarget) { val memwrite = DirectMemoryWrite(add, identifier.position) - val assignTarget = AssignTarget(null, null, memwrite, identifier.position) + val assignTarget = AssignTarget(null, null, memwrite, null, identifier.position) listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent)) } else { val memread = DirectMemoryRead(add, identifier.position) diff --git a/codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt b/codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt index 8523342bf..cadf0ba02 100644 --- a/codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt +++ b/codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt @@ -162,7 +162,7 @@ class StatementOptimizer(private val program: Program, // for loop over a (constant) range of just a single value-- optimize the loop away // loopvar/reg = range value , follow by block val scope = AnonymousScope(mutableListOf(), forLoop.position) - scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position)) + scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position)) scope.statements.addAll(forLoop.body.statements) return listOf(IAstModification.ReplaceNode(forLoop, scope, parent)) } @@ -177,7 +177,7 @@ class StatementOptimizer(private val program: Program, val character = options.compTarget.encodeString(sv.value, sv.encoding)[0] val byte = NumericLiteral(DataType.UBYTE, character.toDouble(), iterable.position) val scope = AnonymousScope(mutableListOf(), forLoop.position) - scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position)) + scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position)) scope.statements.addAll(forLoop.body.statements) return listOf(IAstModification.ReplaceNode(forLoop, scope, parent)) } @@ -190,7 +190,7 @@ class StatementOptimizer(private val program: Program, if(av!=null) { val scope = AnonymousScope(mutableListOf(), forLoop.position) scope.statements.add(Assignment( - AssignTarget(forLoop.loopVar, null, null, forLoop.position), NumericLiteral.optimalInteger(av.toInt(), iterable.position), + AssignTarget(forLoop.loopVar, null, null, null, forLoop.position), NumericLiteral.optimalInteger(av.toInt(), iterable.position), AssignmentOrigin.OPTIMIZER, forLoop.position)) scope.statements.addAll(forLoop.body.statements) return listOf(IAstModification.ReplaceNode(forLoop, scope, parent)) @@ -206,7 +206,7 @@ class StatementOptimizer(private val program: Program, val pos = forLoop.position val loopVar = forLoop.loopVar val addSubOne = BinaryExpression(loopVar.copy(), if(inc) "+" else "-", NumericLiteral.optimalInteger(1, pos), pos, false) - return Assignment(AssignTarget(loopVar.copy(), null, null, pos), addSubOne, AssignmentOrigin.USERCODE, pos) + return Assignment(AssignTarget(loopVar.copy(), null, null, null, pos), addSubOne, AssignmentOrigin.USERCODE, pos) } if (range != null && range.to.constValue(program)?.number == 0.0 && range.step.constValue(program)?.number==-1.0) { @@ -219,7 +219,7 @@ class StatementOptimizer(private val program: Program, val decOne = incOrDec(false) forLoop.body.statements.add(decOne) val replacement = AnonymousScope(mutableListOf( - Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos), + Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, null, pos), fromExpr, AssignmentOrigin.OPTIMIZER, pos), UntilLoop(forLoop.body, condition, pos) ), pos) diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index f507dc420..02606e58f 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -517,32 +517,78 @@ internal class AstChecker(private val program: Program, } override fun visit(assignment: Assignment) { - val targetDt = assignment.target.inferType(program) - val valueDt = assignment.value.inferType(program) - if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) { - if(targetDt.isIterable) - errors.err("cannot assign value to string or array", assignment.value.position) - else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD)) { - if(targetDt.isUnknown) { - if(assignment.target.identifier?.targetStatement(program)!=null) - errors.err("target datatype is unknown", assignment.target.position) - // otherwise, another error about missing symbol is already reported. + fun checkType(target: AssignTarget, value: Expression, augmentable: Boolean) { + val targetDt = target.inferType(program) + val valueDt = value.inferType(program) + if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) { + if(targetDt.isIterable) + errors.err("cannot assign value to string or array", value.position) + else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD)) { + if(targetDt.isUnknown) { + if(target.identifier?.targetStatement(program)!=null) + errors.err("target datatype is unknown", target.position) + // otherwise, another error about missing symbol is already reported. + } } } + + if(value is TypecastExpression) { + if(augmentable && targetDt istype DataType.FLOAT) + errors.err("typecasting a float value in-place makes no sense", value.position) + } + + val numvalue = value.constValue(program) + if(numvalue!=null && targetDt.isKnown) + checkValueTypeAndRange(targetDt.getOr(DataType.UNDEFINED), numvalue) + } + + if(assignment.target.multi==null) { + checkType(assignment.target, assignment.value, assignment.isAugmentable) + } + + + // multi-assign: check the number of assign targets vs. the number of return values of the subroutine + // also check the types of the variables vs the types of each return value + val fcall = assignment.value as? IFunctionCall + val fcallTarget = fcall?.target?.targetSubroutine(program) + if(assignment.target.multi!=null) { + val multi = assignment.target.multi!! + if(fcall==null) { + errors.err("expected a function call with multiple return values", assignment.value.position) + } else { + if(fcallTarget==null) { + errors.err("expected a function call with multiple return values", assignment.value.position) + } else { + if(fcallTarget.returntypes.size!=multi.size) { + errors.err("expected ${multi.size} return values, have ${fcallTarget.returntypes.size}", fcall.position) + } + } + } + + if(errors.noErrors()) { + // check the types... + fcallTarget!!.returntypes.zip(multi).withIndex().forEach { (index, p) -> + val (returnType, target) = p + val targetDt = target.inferType(program).getOr(DataType.UNDEFINED) + if(!(returnType isAssignableTo targetDt)) + errors.err("can't assign returnvalue #${index+1} to corresponding target; ${returnType} vs $targetDt", target.position) + } + } + + } else if(fcallTarget!=null) { + if(fcallTarget.returntypes.size!=1) { + // If there are 2 return values, one of them being a boolean in a status register, this is okay. + // In that case the normal value is assigned and the status bit is dealth with separately for example with if_cs + val (returnRegisters, _) = fcallTarget.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null } + if(returnRegisters.size>1) + errors.err("expected 1 return value, have ${fcallTarget.returntypes.size}", fcall.position) + } } - if(assignment.value is TypecastExpression) { - if(assignment.isAugmentable && targetDt istype DataType.FLOAT) - errors.err("typecasting a float value in-place makes no sense", assignment.value.position) - } - - val numvalue = assignment.value.constValue(program) - if(numvalue!=null && targetDt.isKnown) - checkValueTypeAndRange(targetDt.getOr(DataType.UNDEFINED), numvalue) - super.visit(assignment) } + override fun visit(assignTarget: AssignTarget) { super.visit(assignTarget) @@ -552,6 +598,9 @@ internal class AstChecker(private val program: Program, errors.err("address out of range", assignTarget.position) } + if(assignTarget.parent is AssignTarget) + return // sub-target of a multi-assign is tested elsewhere + val assignment = assignTarget.parent as Statement val targetIdentifier = assignTarget.identifier if (targetIdentifier != null) { @@ -1174,25 +1223,6 @@ internal class AstChecker(private val program: Program, if(error!=null) errors.err(error.first, error.second) - // check the functions that return multiple returnvalues. - val stmt = functionCallExpr.target.targetStatement(program) - if (stmt is Subroutine) { - if (stmt.returntypes.size > 1) { - // Currently, it's only possible to handle ONE (or zero) return values from a subroutine. - // asmsub routines can have multiple return values, for instance in 2 different registers. - // It's not (yet) possible to handle these multiple return values because assignments - // are only to a single unique target at the same time. - // EXCEPTION: - // if the asmsub returns multiple values and one of them is via a status register bit (such as carry), - // it *is* possible to handle them by just actually assigning the register value and - // dealing with the status bit as just being that, the status bit after the call. - val (returnRegisters, _) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null } - if (returnRegisters.size>1) { - errors.err("It's not possible to store the multiple result values of this asmsub call; you should use a small block of custom inline assembly for this.", functionCallExpr.position) - } - } - } - // functions that don't return a value, can't be used in an expression or assignment if(targetStatement is Subroutine) { if(targetStatement.returntypes.isEmpty()) { diff --git a/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt b/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt index 3f0dd7462..9795c0605 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt @@ -121,7 +121,7 @@ class AstPreprocessor(val program: Program, // we need to handle multi-decl here too, the desugarer maybe has not processed it here yet... if(decl.value!=null) { decl.names.forEach { name -> - val target = AssignTarget(IdentifierReference(listOf(name), decl.position), null, null, decl.position) + val target = AssignTarget(IdentifierReference(listOf(name), decl.position), null, null, null, decl.position) val assign = Assignment(target.copy(), decl.value!!.copy(), AssignmentOrigin.VARINIT, decl.position) replacements.add(IAstModification.InsertAfter(decl, assign, scope)) } @@ -137,7 +137,7 @@ class AstPreprocessor(val program: Program, } else { // handle declaration of a single variable if(decl.value!=null && (decl.datatype in NumericDatatypes || decl.datatype==DataType.BOOL)) { - val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position) + val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, null, decl.position) val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position) replacements.add(IAstModification.ReplaceNode(decl, assign, scope)) decl.value = null diff --git a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt index eee646ce0..f27d11713 100644 --- a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt +++ b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt @@ -183,7 +183,7 @@ _after: } if(functionCall.target.nameInSource==listOf("poke")) { // poke(a, v) is synonymous with @(a) = v - val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), position) + val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), null, position) val assign = Assignment(tgt, functionCall.args[1], AssignmentOrigin.OPTIMIZER, position) return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent)) } @@ -222,7 +222,7 @@ _after: return if(parent is AssignTarget) { // assignment to array val memwrite = DirectMemoryWrite(address, arrayIndexedExpression.position) - val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position) + val newtarget = AssignTarget(null, null, memwrite, null, arrayIndexedExpression.position) listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent)) } else { // read from array diff --git a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt index 8b6cea82f..a99564617 100644 --- a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt +++ b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt @@ -90,6 +90,7 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr private fun transform(srcAssign: Assignment): PtNode { if(srcAssign.isAugmentable) { + require(srcAssign.target.multi==null) val srcExpr = srcAssign.value val (operator: String, augmentedValue: Expression?) = when(srcExpr) { is BinaryExpression -> { @@ -136,7 +137,12 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr } val assign = PtAssignment(srcAssign.position) - assign.add(transform(srcAssign.target)) + val multi = srcAssign.target.multi + if(multi==null) { + assign.add(transform(srcAssign.target)) + } else { + multi.forEach { target -> assign.add(transform(target)) } + } assign.add(transformExpression(srcAssign.value)) return assign } @@ -272,11 +278,8 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr private fun transform(srcCall: FunctionCallExpression): PtFunctionCall { val (target, _) = srcCall.target.targetNameAndType(program) - val type = srcCall.inferType(program).getOrElse { - throw FatalAstException("unknown dt $srcCall") - } - val isVoid = type==DataType.UNDEFINED - val call = PtFunctionCall(target, isVoid, type, srcCall.position) + val iType = srcCall.inferType(program) + val call = PtFunctionCall(target, iType.isUnknown, iType.getOrElse { DataType.UNDEFINED }, srcCall.position) for (arg in srcCall.args) call.add(transformExpression(arg)) return call diff --git a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt index 43993e0fb..965ae6f43 100644 --- a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt +++ b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt @@ -60,7 +60,8 @@ internal class StatementReorderer( // Add assignment to initialize with zero // Note: for block-level vars, this will introduce assignments in the block scope. These have to be dealt with correctly later. val identifier = IdentifierReference(listOf(decl.name), decl.position) - val assignzero = Assignment(AssignTarget(identifier, null, null, decl.position), decl.zeroElementValue(), AssignmentOrigin.VARINIT, decl.position) + val assignzero = Assignment(AssignTarget(identifier, null, null, null, decl.position), + decl.zeroElementValue(), AssignmentOrigin.VARINIT, decl.position) return listOf(IAstModification.InsertAfter( decl, assignzero, parent as IStatementContainer )) @@ -72,7 +73,8 @@ internal class StatementReorderer( // So basically consider 'ubyte xx=99' as a short form for 'ubyte xx; xx=99' val pos = decl.value!!.position val identifier = IdentifierReference(listOf(decl.name), pos) - val assign = Assignment(AssignTarget(identifier, null, null, pos), decl.value!!, AssignmentOrigin.VARINIT, pos) + val assign = Assignment(AssignTarget(identifier, null, null, null, pos), + decl.value!!, AssignmentOrigin.VARINIT, pos) decl.value = null return listOf(IAstModification.InsertAfter( decl, assign, parent as IStatementContainer @@ -90,7 +92,8 @@ internal class StatementReorderer( if(target!=null && target.isArray) { val pos = decl.value!!.position val identifier = IdentifierReference(listOf(decl.name), pos) - val assign = Assignment(AssignTarget(identifier, null, null, pos), decl.value!!, AssignmentOrigin.VARINIT, pos) + val assign = Assignment(AssignTarget(identifier, null, null, null, pos), + decl.value!!, AssignmentOrigin.VARINIT, pos) decl.value = null return listOf(IAstModification.InsertAfter( decl, assign, parent as IStatementContainer diff --git a/compiler/test/TestMemory.kt b/compiler/test/TestMemory.kt index ab77ff5d5..ecb9b3b3e 100644 --- a/compiler/test/TestMemory.kt +++ b/compiler/test/TestMemory.kt @@ -36,49 +36,49 @@ class TestMemory: FunSpec({ test("assignment target not in mapped IO space C64") { var memexpr = NumericLiteral.optimalInteger(0x0002, Position.DUMMY) - var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) + var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, Position.DUMMY) var assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) wrapWithProgram(listOf(assign)) target.isIOAddress(c64target.machine) shouldBe false memexpr = NumericLiteral.optimalInteger(0x1000, Position.DUMMY) - target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) + target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, Position.DUMMY) assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) wrapWithProgram(listOf(assign)) target.isIOAddress(c64target.machine) shouldBe false memexpr = NumericLiteral.optimalInteger(0x9fff, Position.DUMMY) - target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) + target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, Position.DUMMY) assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) wrapWithProgram(listOf(assign)) target.isIOAddress(c64target.machine) shouldBe false memexpr = NumericLiteral.optimalInteger(0xa000, Position.DUMMY) - target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) + target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, Position.DUMMY) assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) wrapWithProgram(listOf(assign)) target.isIOAddress(c64target.machine) shouldBe false memexpr = NumericLiteral.optimalInteger(0xc000, Position.DUMMY) - target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) + target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, Position.DUMMY) assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) wrapWithProgram(listOf(assign)) target.isIOAddress(c64target.machine) shouldBe false memexpr = NumericLiteral.optimalInteger(0xcfff, Position.DUMMY) - target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) + target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, Position.DUMMY) assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) wrapWithProgram(listOf(assign)) target.isIOAddress(c64target.machine) shouldBe false memexpr = NumericLiteral.optimalInteger(0xeeee, Position.DUMMY) - target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) + target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, Position.DUMMY) assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) wrapWithProgram(listOf(assign)) target.isIOAddress(c64target.machine) shouldBe false memexpr = NumericLiteral.optimalInteger(0xffff, Position.DUMMY) - target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) + target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, Position.DUMMY) assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) wrapWithProgram(listOf(assign)) target.isIOAddress(c64target.machine) shouldBe false @@ -87,25 +87,25 @@ class TestMemory: FunSpec({ test("assign target in mapped IO space C64") { var memexpr = NumericLiteral.optimalInteger(0x0000, Position.DUMMY) - var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) + var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, Position.DUMMY) var assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) wrapWithProgram(listOf(assign)) target.isIOAddress(c64target.machine) shouldBe true memexpr = NumericLiteral.optimalInteger(0x0001, Position.DUMMY) - target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) + target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, Position.DUMMY) assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) wrapWithProgram(listOf(assign)) target.isIOAddress(c64target.machine) shouldBe true memexpr = NumericLiteral.optimalInteger(0xd000, Position.DUMMY) - target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) + target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, Position.DUMMY) assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) wrapWithProgram(listOf(assign)) target.isIOAddress(c64target.machine) shouldBe true memexpr = NumericLiteral.optimalInteger(0xdfff, Position.DUMMY) - target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) + target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, Position.DUMMY) assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) wrapWithProgram(listOf(assign)) target.isIOAddress(c64target.machine) shouldBe true @@ -114,7 +114,7 @@ class TestMemory: FunSpec({ fun createTestProgramForMemoryRefViaVar(address: UInt, vartype: VarDeclType): AssignTarget { val decl = VarDecl(vartype, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) val memexpr = IdentifierReference(listOf("address"), Position.DUMMY) - val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) + val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, Position.DUMMY) val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) wrapWithProgram(listOf(decl, assignment)) return target @@ -137,13 +137,13 @@ class TestMemory: FunSpec({ test("memory expression mapped to IO memory on C64") { var memexpr = PrefixExpression("+", NumericLiteral.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY) - var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) + var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, Position.DUMMY) var assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) wrapWithProgram(listOf(assign)) target.isIOAddress(c64target.machine) shouldBe false memexpr = PrefixExpression("+", NumericLiteral.optimalInteger(0xd020, Position.DUMMY), Position.DUMMY) - target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) + target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, Position.DUMMY) assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) wrapWithProgram(listOf(assign)) target.isIOAddress(c64target.machine) shouldBe true @@ -151,7 +151,7 @@ class TestMemory: FunSpec({ test("regular variable not in mapped IO ram on C64") { val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", emptyList(), null, false, false, Position.DUMMY) - val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) + val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, null, Position.DUMMY) val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) val subroutine = Subroutine("test", mutableListOf(), mutableListOf(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY) val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test")) @@ -163,7 +163,7 @@ class TestMemory: FunSpec({ test("memory mapped variable not in mapped IO ram on C64") { val address = 0x1000u val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) - val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) + val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, null, Position.DUMMY) val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) val subroutine = Subroutine("test", mutableListOf(), mutableListOf(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY) val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test")) @@ -175,7 +175,7 @@ class TestMemory: FunSpec({ test("memory mapped variable in mapped IO ram on C64") { val address = 0xd020u val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) - val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) + val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, null, Position.DUMMY) val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) val subroutine = Subroutine("test", mutableListOf(), mutableListOf(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY) val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test")) @@ -187,7 +187,7 @@ class TestMemory: FunSpec({ test("array not in mapped IO ram") { val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", emptyList(), null, false, false, Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) - val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) + val target = AssignTarget(null, arrayindexed, null, null, Position.DUMMY) val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) val subroutine = Subroutine("test", mutableListOf(), mutableListOf(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY) val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test")) @@ -200,7 +200,7 @@ class TestMemory: FunSpec({ val address = 0x1000u val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) - val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) + val target = AssignTarget(null, arrayindexed, null, null, Position.DUMMY) val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) val subroutine = Subroutine("test", mutableListOf(), mutableListOf(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY) val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test")) @@ -213,7 +213,7 @@ class TestMemory: FunSpec({ val address = 0xd800u val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) - val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) + val target = AssignTarget(null, arrayindexed, null, null, Position.DUMMY) val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) val subroutine = Subroutine("test", mutableListOf(), mutableListOf(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY) val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test")) diff --git a/compiler/test/codegeneration/TestAsmGenSymbols.kt b/compiler/test/codegeneration/TestAsmGenSymbols.kt index 7a6d93e36..ad5877620 100644 --- a/compiler/test/codegeneration/TestAsmGenSymbols.kt +++ b/compiler/test/codegeneration/TestAsmGenSymbols.kt @@ -50,7 +50,7 @@ class TestAsmGenSymbols: StringSpec({ val var2InSub = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", emptyList(), null, false, false, Position.DUMMY) val labelInSub = Label("locallabel", Position.DUMMY) - val tgt = AssignTarget(IdentifierReference(listOf("tgt"), Position.DUMMY), null, null, Position.DUMMY) + val tgt = AssignTarget(IdentifierReference(listOf("tgt"), Position.DUMMY), null, null, null, Position.DUMMY) val assign1 = Assignment(tgt, IdentifierReference(listOf("localvar"), Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) val assign2 = Assignment(tgt, AddressOf(IdentifierReference(listOf("locallabel"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) val assign3 = Assignment(tgt, AddressOf(IdentifierReference(listOf("var_outside"), Position.DUMMY), null, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY) diff --git a/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt b/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt index f95b4b6a5..e486e5842 100644 --- a/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt +++ b/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt @@ -409,6 +409,14 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program: assignTarget.memoryAddress?.accept(this) assignTarget.identifier?.accept(this) assignTarget.arrayindexed?.accept(this) + val multi = assignTarget.multi + if(multi!=null) { + multi.dropLast(1).forEach { target -> + target.accept(this) + output(", ") + } + multi.last().accept(this) + } } override fun visit(scope: AnonymousScope) { diff --git a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt index 64d54ad5a..5908ffa2e 100644 --- a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt +++ b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt @@ -322,20 +322,25 @@ private fun Sub_paramsContext.toAst(): List = private fun Assign_targetContext.toAst() : AssignTarget { return when(this) { is IdentifierTargetContext -> - AssignTarget(scoped_identifier().toAst(), null, null, scoped_identifier().toPosition()) + AssignTarget(scoped_identifier().toAst(), null, null, null, scoped_identifier().toPosition()) is MemoryTargetContext -> - AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), directmemory().toPosition()), toPosition()) + AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), directmemory().toPosition()), null, toPosition()) is ArrayindexedTargetContext -> { val ax = arrayindexed() val arrayvar = ax.scoped_identifier().toAst() val index = ax.arrayindex().toAst() val arrayindexed = ArrayIndexedExpression(arrayvar, index, ax.toPosition()) - AssignTarget(null, arrayindexed, null, toPosition()) + AssignTarget(null, arrayindexed, null, null, toPosition()) } else -> throw FatalAstException("weird assign target node $this") } } +private fun Multi_assign_targetContext.toAst() : AssignTarget { + val targets = this.assign_target().map { it.toAst() } + return AssignTarget(null, null, null, targets, toPosition()) +} + private fun ClobberContext.toAst() : Set { val names = this.NAME().map { it.text } try { @@ -346,6 +351,11 @@ private fun ClobberContext.toAst() : Set { } private fun AssignmentContext.toAst(): Statement { + val multiAssign = multi_assign_target() + if(multiAssign!=null) { + return Assignment(multiAssign.toAst(), expression().toAst(), AssignmentOrigin.USERCODE, toPosition()) + } + val nestedAssign = assignment() if(nestedAssign==null) return Assignment(assign_target().toAst(), expression().toAst(), AssignmentOrigin.USERCODE, toPosition()) diff --git a/compilerAst/src/prog8/ast/statements/AstStatements.kt b/compilerAst/src/prog8/ast/statements/AstStatements.kt index c4985bff3..9d9b20374 100644 --- a/compilerAst/src/prog8/ast/statements/AstStatements.kt +++ b/compilerAst/src/prog8/ast/statements/AstStatements.kt @@ -493,6 +493,7 @@ class Assignment(var target: AssignTarget, var value: Expression, var origin: As data class AssignTarget(var identifier: IdentifierReference?, var arrayindexed: ArrayIndexedExpression?, val memoryAddress: DirectMemoryWrite?, + val multi: List?, override val position: Position) : Node { override lateinit var parent: Node @@ -501,12 +502,14 @@ data class AssignTarget(var identifier: IdentifierReference?, identifier?.linkParents(this) arrayindexed?.linkParents(this) memoryAddress?.linkParents(this) + multi?.forEach { it.linkParents(this) } } override fun replaceChildNode(node: Node, replacement: Node) { when { node === identifier -> identifier = replacement as IdentifierReference node === arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression + node === multi -> throw FatalAstException("can't replace multi assign targets") else -> throw FatalAstException("invalid replace") } replacement.parent = this @@ -514,11 +517,12 @@ data class AssignTarget(var identifier: IdentifierReference?, fun accept(visitor: IAstVisitor) = visitor.visit(this) fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) - override fun copy() = AssignTarget(identifier?.copy(), arrayindexed?.copy(), memoryAddress?.copy(), position) + override fun copy() = AssignTarget(identifier?.copy(), arrayindexed?.copy(), memoryAddress?.copy(), multi?.toList(), position) override fun referencesIdentifier(nameInSource: List): Boolean = identifier?.referencesIdentifier(nameInSource)==true || arrayindexed?.referencesIdentifier(nameInSource)==true || - memoryAddress?.referencesIdentifier(nameInSource)==true + memoryAddress?.referencesIdentifier(nameInSource)==true || + multi?.any { it.referencesIdentifier(nameInSource)}==true fun inferType(program: Program): InferredTypes.InferredType { if (identifier != null) { @@ -533,6 +537,8 @@ data class AssignTarget(var identifier: IdentifierReference?, if (memoryAddress != null) return InferredTypes.knownFor(DataType.UBYTE) + // a multi-assign has no 1 type... TODO although it could perhaps be the type of the first target? + return InferredTypes.unknown() } @@ -542,7 +548,8 @@ data class AssignTarget(var identifier: IdentifierReference?, identifier != null -> identifier!!.copy() arrayindexed != null -> arrayindexed!!.copy() memoryAddress != null -> DirectMemoryRead(memoryAddress.addressExpression.copy(), memoryAddress.position) - else -> throw FatalAstException("invalid assignmenttarget $this") + multi != null -> throw FatalAstException("cannot turn a multi-assign into a single source expression") + else -> throw FatalAstException("invalid assignmenttarget") } } @@ -562,6 +569,7 @@ data class AssignTarget(var identifier: IdentifierReference?, else false } + multi != null -> false else -> false } } @@ -583,6 +591,9 @@ data class AssignTarget(var identifier: IdentifierReference?, return x1 != null && x2 != null && x1 == x2 } } + if(this.multi != null && other.multi != null) + return this.multi == other.multi + return false } @@ -624,6 +635,9 @@ data class AssignTarget(var identifier: IdentifierReference?, else false } + multi != null -> { + return multi.any { it.isIOAddress(machine) } + } else -> return false } } diff --git a/compilerAst/src/prog8/ast/walk/AstWalker.kt b/compilerAst/src/prog8/ast/walk/AstWalker.kt index 282d83c31..f690ffa93 100644 --- a/compilerAst/src/prog8/ast/walk/AstWalker.kt +++ b/compilerAst/src/prog8/ast/walk/AstWalker.kt @@ -432,6 +432,7 @@ abstract class AstWalker { assignTarget.arrayindexed?.accept(this, assignTarget) assignTarget.identifier?.accept(this, assignTarget) assignTarget.memoryAddress?.accept(this, assignTarget) + assignTarget.multi?.forEach { it.accept(this, assignTarget) } track(after(assignTarget, parent), assignTarget, parent) } diff --git a/compilerAst/src/prog8/ast/walk/IAstVisitor.kt b/compilerAst/src/prog8/ast/walk/IAstVisitor.kt index 3a25955ea..a041f9183 100644 --- a/compilerAst/src/prog8/ast/walk/IAstVisitor.kt +++ b/compilerAst/src/prog8/ast/walk/IAstVisitor.kt @@ -152,6 +152,7 @@ interface IAstVisitor { assignTarget.arrayindexed?.accept(this) assignTarget.identifier?.accept(this) assignTarget.memoryAddress?.accept(this) + assignTarget.multi?.forEach { it.accept(this) } } fun visit(scope: AnonymousScope) { diff --git a/docs/source/todo.rst b/docs/source/todo.rst index de4be414b..e7e48bad0 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -66,7 +66,6 @@ Optimizations: those checks should probably be removed, or be made permanent - optimizeCommonSubExpressions: currently only looks in expressions on a single line, could search across multiple expressions - STRUCTS again? -------------- diff --git a/parser/antlr/Prog8ANTLR.g4 b/parser/antlr/Prog8ANTLR.g4 index c202dd092..5723a747c 100644 --- a/parser/antlr/Prog8ANTLR.g4 +++ b/parser/antlr/Prog8ANTLR.g4 @@ -150,7 +150,7 @@ datatype: 'ubyte' | 'byte' | 'uword' | 'word' | 'float' | 'str' | 'bool' ; arrayindex: '[' expression ']' ; -assignment : (assign_target '=' expression) | (assign_target '=' assignment); +assignment : (assign_target '=' expression) | (assign_target '=' assignment) | (multi_assign_target '=' expression); augassignment : assign_target operator=('+=' | '-=' | '/=' | '*=' | '&=' | '|=' | '^=' | '%=' | '<<=' | '>>=' ) expression @@ -162,6 +162,9 @@ assign_target: | directmemory #MemoryTarget ; +multi_assign_target: + assign_target (',' assign_target)+ ; + postincrdecr : assign_target operator = ('++' | '--') ; expression :