mirror of
https://github.com/irmen/prog8.git
synced 2025-02-08 16:30:28 +00:00
be able to parse multiple return values (sub + return)
This commit is contained in:
parent
35d9412559
commit
ca9422bbe9
@ -130,17 +130,7 @@ class PtRepeatLoop(position: Position) : PtNode(position) {
|
||||
}
|
||||
|
||||
|
||||
class PtReturn(position: Position) : PtNode(position) {
|
||||
val hasValue: Boolean
|
||||
get() = children.any()
|
||||
val value: PtExpression?
|
||||
get() {
|
||||
return if(children.any())
|
||||
children.single() as PtExpression
|
||||
else
|
||||
null
|
||||
}
|
||||
}
|
||||
class PtReturn(position: Position) : PtNode(position) // children are all expressions
|
||||
|
||||
|
||||
sealed interface IPtVariable {
|
||||
|
@ -1051,11 +1051,12 @@ $repeatLabel""")
|
||||
}
|
||||
|
||||
private fun translate(ret: PtReturn) {
|
||||
ret.value?.let { returnvalue ->
|
||||
val returnvalue = ret.children.singleOrNull()
|
||||
if(returnvalue!=null) {
|
||||
val sub = ret.definingSub()!!
|
||||
val returnReg = sub.returnRegister()!!
|
||||
if (sub.returntype?.isNumericOrBool==true) {
|
||||
assignExpressionToRegister(returnvalue, returnReg.registerOrPair!!)
|
||||
assignExpressionToRegister(returnvalue as PtExpression, returnReg.registerOrPair!!)
|
||||
}
|
||||
else {
|
||||
// all else take its address and assign that also to AY register pair
|
||||
@ -1065,6 +1066,9 @@ $repeatLabel""")
|
||||
assignmentAsmGen.assignExpressionToRegister(addrofValue, returnReg.registerOrPair!!, false)
|
||||
}
|
||||
}
|
||||
else if(ret.children.size>1) {
|
||||
TODO("multi-value return")
|
||||
}
|
||||
out(" rts")
|
||||
}
|
||||
|
||||
|
@ -1758,10 +1758,14 @@ class IRCodeGen(
|
||||
|
||||
private fun translate(ret: PtReturn): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val value = ret.value
|
||||
if(ret.children.size>1) {
|
||||
TODO("multi-value return")
|
||||
}
|
||||
val value = ret.children.singleOrNull()
|
||||
if(value==null) {
|
||||
addInstr(result, IRInstruction(Opcode.RETURN), null)
|
||||
} else {
|
||||
value as PtExpression
|
||||
if(value.type.isFloat) {
|
||||
if(value is PtNumber) {
|
||||
addInstr(result, IRInstruction(Opcode.RETURNI, IRDataType.FLOAT, immediateFp = value.number), null)
|
||||
|
@ -13,7 +13,7 @@ import prog8.code.core.InternalCompilerException
|
||||
import prog8.code.target.VMTarget
|
||||
|
||||
|
||||
private fun isEmptyReturn(stmt: Statement): Boolean = stmt is Return && stmt.value==null
|
||||
private fun isEmptyReturn(stmt: Statement): Boolean = stmt is Return && stmt.values.size==0
|
||||
|
||||
|
||||
// inliner potentially enables *ONE LINED* subroutines, wihtout to be inlined.
|
||||
@ -38,26 +38,21 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
// subroutine is possible candidate to be inlined
|
||||
subroutine.inline =
|
||||
when (val stmt = subroutine.statements[0]) {
|
||||
is Return -> {
|
||||
if (stmt.value is NumericLiteral)
|
||||
// TODO consider multi-value returns as well
|
||||
is Return -> stmt.values.isEmpty() || stmt.values.size==1 &&
|
||||
if (stmt.values[0] is NumericLiteral)
|
||||
true
|
||||
else if (stmt.value == null)
|
||||
else if (stmt.values[0] is IdentifierReference) {
|
||||
makeFullyScoped(stmt.values[0] as IdentifierReference)
|
||||
true
|
||||
else if (stmt.value is IdentifierReference) {
|
||||
makeFullyScoped(stmt.value as IdentifierReference)
|
||||
true
|
||||
} else if (stmt.value!! is IFunctionCall && (stmt.value as IFunctionCall).args.size <= 1 && (stmt.value as IFunctionCall).args.all { it is NumericLiteral || it is IdentifierReference }) {
|
||||
when (stmt.value) {
|
||||
is FunctionCallExpression -> {
|
||||
makeFullyScoped(stmt.value as FunctionCallExpression)
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
} else if (stmt.values[0]!! is IFunctionCall && (stmt.values[0] as IFunctionCall).args.size <= 1 && (stmt.values[0] as IFunctionCall).args.all { it is NumericLiteral || it is IdentifierReference }) {
|
||||
if (stmt.values[0] is FunctionCallExpression) {
|
||||
makeFullyScoped(stmt.values[0] as FunctionCallExpression)
|
||||
true
|
||||
}
|
||||
else false
|
||||
} else
|
||||
false
|
||||
}
|
||||
|
||||
is Assignment -> {
|
||||
if (stmt.value.isSimple) {
|
||||
@ -182,14 +177,19 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
// note that we don't have to process any args, because we online inline parameterless subroutines.
|
||||
when (val toInline = sub.statements.first()) {
|
||||
is Return -> {
|
||||
val fcall = toInline.value as? FunctionCallExpression
|
||||
if(fcall!=null) {
|
||||
// insert the function call expression as a void function call directly
|
||||
sub.hasBeenInlined=true
|
||||
val call = FunctionCallStatement(fcall.target.copy(), fcall.args.map { it.copy() }.toMutableList(), true, fcall.position)
|
||||
listOf(IAstModification.ReplaceNode(origNode, call, parent))
|
||||
} else
|
||||
// TODO consider multi-value returns as well
|
||||
if(toInline.values.size!=1)
|
||||
noModifications
|
||||
else {
|
||||
val fcall = toInline.values[0] as? FunctionCallExpression
|
||||
if(fcall!=null) {
|
||||
// insert the function call expression as a void function call directly
|
||||
sub.hasBeenInlined=true
|
||||
val call = FunctionCallStatement(fcall.target.copy(), fcall.args.map { it.copy() }.toMutableList(), true, fcall.position)
|
||||
listOf(IAstModification.ReplaceNode(origNode, call, parent))
|
||||
} else
|
||||
noModifications
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if(origNode !== toInline) {
|
||||
@ -226,9 +226,10 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
is Return -> {
|
||||
// is an expression, so we have to have a Return here in the inlined sub
|
||||
// note that we don't have to process any args, because we online inline parameterless subroutines.
|
||||
if(toInline.value!=null && functionCallExpr!==toInline.value) {
|
||||
// TODO consider multi-value returns as well
|
||||
if(toInline.values.size==1 && functionCallExpr!==toInline.values[0]) {
|
||||
sub.hasBeenInlined=true
|
||||
listOf(IAstModification.ReplaceNode(functionCallExpr, toInline.value!!.copy(), parent))
|
||||
listOf(IAstModification.ReplaceNode(functionCallExpr, toInline.values[0].copy(), parent))
|
||||
}
|
||||
else
|
||||
noModifications
|
||||
|
@ -122,25 +122,25 @@ internal class AstChecker(private val program: Program,
|
||||
throw FatalAstException("cannot use a return with one value in a subroutine that has multiple return values: $returnStmt")
|
||||
}
|
||||
|
||||
if(expectedReturnValues.isEmpty() && returnStmt.value!=null) {
|
||||
errors.err("invalid number of return values", returnStmt.position)
|
||||
if(returnStmt.values.size<expectedReturnValues.size) {
|
||||
errors.err("too few return values for the subroutine: expected ${expectedReturnValues.size} got ${returnStmt.values.size}", returnStmt.position)
|
||||
}
|
||||
if(expectedReturnValues.isNotEmpty() && returnStmt.value==null) {
|
||||
errors.err("invalid number of return values", returnStmt.position)
|
||||
else if(returnStmt.values.size>expectedReturnValues.size) {
|
||||
errors.err("too many return values for the subroutine: expected ${expectedReturnValues.size} got ${returnStmt.values.size}", returnStmt.position)
|
||||
}
|
||||
if(expectedReturnValues.size==1 && returnStmt.value!=null) {
|
||||
val valueDt = returnStmt.value!!.inferType(program)
|
||||
for((expectedDt, actual) in expectedReturnValues.zip(returnStmt.values)) {
|
||||
val valueDt = actual.inferType(program)
|
||||
if(valueDt.isKnown) {
|
||||
if (expectedReturnValues[0] != valueDt.getOrUndef()) {
|
||||
if(valueDt.isBool && expectedReturnValues[0].isUnsignedByte) {
|
||||
if (expectedDt != valueDt.getOrUndef()) {
|
||||
if(valueDt.isBool && expectedDt.isUnsignedByte) {
|
||||
// if the return value is a bool and the return type is ubyte, allow this. But give a warning.
|
||||
errors.info("return type of the subroutine should probably be bool instead of ubyte", returnStmt.position)
|
||||
} else if(valueDt.isIterable && expectedReturnValues[0].isUnsignedWord) {
|
||||
errors.info("return type of the subroutine should probably be bool instead of ubyte", actual.position)
|
||||
} else if(valueDt.isIterable && expectedDt.isUnsignedWord) {
|
||||
// you can return a string or array when an uword (pointer) is returned
|
||||
} else if(valueDt issimpletype BaseDataType.UWORD && expectedReturnValues[0].isString) {
|
||||
} else if(valueDt issimpletype BaseDataType.UWORD && expectedDt.isString) {
|
||||
// you can return an uword pointer when the return type is a string
|
||||
} else {
|
||||
errors.err("type $valueDt of return value doesn't match subroutine's return type ${expectedReturnValues[0]}",returnStmt.value!!.position)
|
||||
errors.err("type $valueDt of return value doesn't match subroutine's return type ${expectedDt}", actual.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,14 +51,14 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
|
||||
// and if an assembly block doesn't contain a rts/rti.
|
||||
if (!subroutine.isAsmSubroutine) {
|
||||
if(subroutine.isEmpty()) {
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
val returnStmt = Return(arrayOf(), subroutine.position)
|
||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||
} else {
|
||||
val last = subroutine.statements.last()
|
||||
if((last !is InlineAssembly || !last.hasReturnOrRts()) && last !is Return) {
|
||||
val lastStatement = subroutine.statements.reversed().firstOrNull { it !is Subroutine }
|
||||
if(lastStatement !is Return) {
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
val returnStmt = Return(arrayOf(), subroutine.position)
|
||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||
}
|
||||
}
|
||||
@ -76,7 +76,7 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
|
||||
&& prevStmt !is Subroutine
|
||||
&& prevStmt !is Return
|
||||
) {
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
val returnStmt = Return(arrayOf(), subroutine.position)
|
||||
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
|
||||
}
|
||||
}
|
||||
|
@ -484,8 +484,7 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
|
||||
|
||||
private fun transform(srcNode: Return): PtReturn {
|
||||
val ret = PtReturn(srcNode.position)
|
||||
if(srcNode.value!=null)
|
||||
ret.add(transformExpression(srcNode.value!!))
|
||||
srcNode.values.forEach { ret.add(transformExpression(it)) }
|
||||
return ret
|
||||
}
|
||||
|
||||
|
@ -143,14 +143,17 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
|
||||
|
||||
// return exits
|
||||
for(ret in returnsToAugment) {
|
||||
val value = ret.value
|
||||
if(value==null || notComplex(value)) {
|
||||
if(ret.children.isEmpty() || ret.children.all { notComplex(it as PtExpression) }) {
|
||||
invokedeferbefore(ret)
|
||||
continue
|
||||
}
|
||||
|
||||
// complex return value, need to store it before calling the defer block
|
||||
val (pushCall, popCall) = makePushPopFunctionCalls(value)
|
||||
if(ret.children.size>1) {
|
||||
TODO("multi-value return ; defer")
|
||||
}
|
||||
|
||||
val (pushCall, popCall) = makePushPopFunctionCalls(ret.children[0] as PtExpression)
|
||||
val newRet = PtReturn(ret.position)
|
||||
newRet.add(popCall)
|
||||
val group = PtNodeGroup()
|
||||
|
@ -327,31 +327,44 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
// add a typecast to the return type if it doesn't match the subroutine's signature
|
||||
// but only if no data loss occurs
|
||||
val returnValue = returnStmt.value
|
||||
if (returnStmt.values.isEmpty())
|
||||
return noModifications
|
||||
val subroutine = returnStmt.definingSubroutine!!
|
||||
if (subroutine.returntypes.size != returnStmt.values.size)
|
||||
return noModifications
|
||||
|
||||
for((index, pair) in returnStmt.values.zip(subroutine.returntypes).withIndex()) {
|
||||
val (returnValue, subReturnType) = pair
|
||||
println("$index $returnValue -> $subReturnType")
|
||||
}
|
||||
|
||||
// 1 or more return values to check.
|
||||
val returnValue = returnStmt.values.singleOrNull()
|
||||
if(returnValue!=null) {
|
||||
val subroutine = returnStmt.definingSubroutine!!
|
||||
if(subroutine.returntypes.size==1) {
|
||||
val subReturnType = subroutine.returntypes.first()
|
||||
val returnDt = returnValue.inferType(program)
|
||||
if(!(returnDt istype subReturnType) && returnValue is NumericLiteral) {
|
||||
// see if we might change the returnvalue into the expected type
|
||||
val castedValue = returnValue.convertTypeKeepValue(subReturnType.base)
|
||||
if(castedValue.isValid) {
|
||||
return listOf(IAstModification.ReplaceNode(returnValue, castedValue.valueOrZero(), returnStmt))
|
||||
}
|
||||
}
|
||||
if (returnDt istype subReturnType or returnDt.isNotAssignableTo(subReturnType))
|
||||
return noModifications
|
||||
if (returnValue is NumericLiteral) {
|
||||
val cast = returnValue.cast(subReturnType.base, true)
|
||||
if(cast.isValid)
|
||||
returnStmt.value = cast.valueOrZero()
|
||||
} else {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
addTypecastOrCastedValueModification(modifications, returnValue, subReturnType.base, returnStmt)
|
||||
return modifications
|
||||
val subReturnType = subroutine.returntypes.single()
|
||||
val returnDt = returnValue.inferType(program)
|
||||
if(!(returnDt istype subReturnType) && returnValue is NumericLiteral) {
|
||||
// see if we might change the returnvalue into the expected type
|
||||
val castedValue = returnValue.convertTypeKeepValue(subReturnType.base)
|
||||
if(castedValue.isValid) {
|
||||
return listOf(IAstModification.ReplaceNode(returnValue, castedValue.valueOrZero(), returnStmt))
|
||||
}
|
||||
}
|
||||
if (returnDt istype subReturnType or returnDt.isNotAssignableTo(subReturnType))
|
||||
return noModifications
|
||||
if (returnValue is NumericLiteral) {
|
||||
val cast = returnValue.cast(subReturnType.base, true)
|
||||
if(cast.isValid) {
|
||||
returnStmt.values[0] = cast.valueOrZero()
|
||||
}
|
||||
} else {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
addTypecastOrCastedValueModification(modifications, returnValue, subReturnType.base, returnStmt)
|
||||
return modifications
|
||||
}
|
||||
}
|
||||
else if(returnStmt.values.size>1) {
|
||||
TODO("multi-value return ; typecast")
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
@ -961,7 +961,7 @@ main {
|
||||
val ifscope_return = ifscope.children[2] as PtReturn
|
||||
ifscope_defer.name shouldBe "p8b_main.p8s_test.p8s_prog8_invoke_defers"
|
||||
ifscope_push.name shouldBe "sys.pushw"
|
||||
(ifscope_return.value as PtFunctionCall).name shouldBe "sys.popw"
|
||||
(ifscope_return.children.single() as PtFunctionCall).name shouldBe "sys.popw"
|
||||
|
||||
val ending = sub.children[6] as PtFunctionCall
|
||||
ending.name shouldBe "p8b_main.p8s_test.p8s_prog8_invoke_defers"
|
||||
|
@ -399,8 +399,10 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
}
|
||||
|
||||
override fun visit(returnStmt: Return) {
|
||||
output("return ")
|
||||
returnStmt.value?.accept(this)
|
||||
if(returnStmt.values.isEmpty())
|
||||
output("return")
|
||||
else
|
||||
output("return ${returnStmt.values.map{it.accept(this)}.joinToString(", ")}")
|
||||
}
|
||||
|
||||
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
|
@ -298,7 +298,8 @@ private fun InlineirContext.toAst(): InlineAssembly {
|
||||
}
|
||||
|
||||
private fun ReturnstmtContext.toAst() : Return {
|
||||
return Return(expression()?.toAst(), toPosition())
|
||||
val values = if(returnvalues()==null || returnvalues().expression().size==0) arrayOf<Expression>() else returnvalues().expression().map { it.toAst() }.toTypedArray()
|
||||
return Return(values, toPosition())
|
||||
}
|
||||
|
||||
private fun UnconditionaljumpContext.toAst(): Jump {
|
||||
@ -313,11 +314,11 @@ private fun AliasContext.toAst(): Statement =
|
||||
|
||||
private fun SubroutineContext.toAst() : Subroutine {
|
||||
// non-asm subroutine
|
||||
val returntype = sub_return_part()?.datatype()?.toAst()
|
||||
val returntypes = sub_return_part()?.datatype()?.map { it.toAst() } ?: emptyList()
|
||||
return Subroutine(
|
||||
identifier().text,
|
||||
sub_params()?.toAst()?.toMutableList() ?: mutableListOf(),
|
||||
if (returntype == null) mutableListOf() else mutableListOf(DataType.forDt(returntype)),
|
||||
returntypes.map { DataType.forDt(it) }.toMutableList(),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptySet(),
|
||||
|
@ -4,6 +4,7 @@ import prog8.ast.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.ast.PtExpression
|
||||
import prog8.code.core.*
|
||||
import java.util.*
|
||||
|
||||
@ -174,25 +175,26 @@ data class Label(override val name: String, override val position: Position) : S
|
||||
override fun toString()= "Label(name=$name, pos=$position)"
|
||||
}
|
||||
|
||||
class Return(var value: Expression?, override val position: Position) : Statement() {
|
||||
class Return(val values: Array<Expression>, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
value?.linkParents(this)
|
||||
values.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression)
|
||||
value = replacement
|
||||
replacement.parent = this
|
||||
val index = values.indexOf(node)
|
||||
if(replacement is Expression && index>=0) {
|
||||
values[index] = replacement
|
||||
} else throw FatalAstException("invalid replace")
|
||||
}
|
||||
|
||||
override fun copy() = Return(value?.copy(), position)
|
||||
override fun referencesIdentifier(nameInSource: List<String>): Boolean = value?.referencesIdentifier(nameInSource)==true
|
||||
override fun copy() = Return(values.map { it.copy() }.toTypedArray(), position)
|
||||
override fun referencesIdentifier(nameInSource: List<String>): Boolean = values.any{ it.referencesIdentifier(nameInSource) }
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
override fun toString() = "Return($value, pos=$position)"
|
||||
override fun toString() = "Return($values, pos=$position)"
|
||||
}
|
||||
|
||||
class Break(override val position: Position) : Statement() {
|
||||
|
@ -412,7 +412,7 @@ abstract class AstWalker {
|
||||
|
||||
fun visit(returnStmt: Return, parent: Node) {
|
||||
track(before(returnStmt, parent), returnStmt, parent)
|
||||
returnStmt.value?.accept(this, returnStmt)
|
||||
returnStmt.values.forEach { it -> it.accept(this, returnStmt) }
|
||||
track(after(returnStmt, parent), returnStmt, parent)
|
||||
}
|
||||
|
||||
|
@ -142,7 +142,7 @@ interface IAstVisitor {
|
||||
}
|
||||
|
||||
fun visit(returnStmt: Return) {
|
||||
returnStmt.value?.accept(this)
|
||||
returnStmt.values.forEach { it->it.accept(this) }
|
||||
}
|
||||
|
||||
fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
|
@ -1,6 +1,8 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
- implement the TODO multi-value return occurences.
|
||||
|
||||
- add paypal donation button as well?
|
||||
- announce prog8 on the 6502.org site?
|
||||
|
||||
|
@ -1,16 +1,17 @@
|
||||
%import textio
|
||||
%option no_sysinit
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
cx16.r0L, cx16.r1, cx16.r2 = multiasm()
|
||||
cx16.r0L = multiasm()
|
||||
cx16.r0,cx16.r1 = single()
|
||||
cx16.r0 = multi()
|
||||
}
|
||||
|
||||
asmsub multiasm() -> ubyte @A, uword @R1 {
|
||||
%asm {{
|
||||
rts
|
||||
}}
|
||||
sub single() -> uword {
|
||||
return 42+cx16.r0L
|
||||
}
|
||||
|
||||
sub multi() -> uword, uword {
|
||||
return 42+cx16.r0L, 99
|
||||
}
|
||||
}
|
||||
|
@ -222,7 +222,9 @@ expression_list :
|
||||
expression (',' EOL? expression)* // you can split the expression list over several lines
|
||||
;
|
||||
|
||||
returnstmt : 'return' expression? ;
|
||||
returnstmt : 'return' returnvalues? ;
|
||||
|
||||
returnvalues: expression (',' expression)* ;
|
||||
|
||||
breakstmt : 'break';
|
||||
|
||||
@ -264,7 +266,7 @@ subroutine :
|
||||
'sub' identifier '(' sub_params? ')' sub_return_part? EOL? (statement_block EOL?)
|
||||
;
|
||||
|
||||
sub_return_part : '->' datatype ;
|
||||
sub_return_part : '->' datatype (',' datatype)* ;
|
||||
|
||||
statement_block :
|
||||
'{' EOL?
|
||||
|
Loading…
x
Reference in New Issue
Block a user