mirror of
https://github.com/irmen/prog8.git
synced 2024-12-24 16:29:21 +00:00
translating subroutine calls and returns
This commit is contained in:
parent
bf5c2e07a2
commit
1c036c4813
@ -1,32 +1,18 @@
|
||||
%option enable_floats
|
||||
|
||||
%import c64lib
|
||||
%import mathlib
|
||||
%import prog8lib
|
||||
|
||||
~ main {
|
||||
|
||||
sub start() -> () {
|
||||
|
||||
const word yoffset=100
|
||||
const float height=20.2
|
||||
word pixely
|
||||
float yy
|
||||
float v
|
||||
|
||||
yy = 11.0-(v-22.0)
|
||||
yy = 11.0-(22.0-v)
|
||||
yy = (v-22.0)-11.0
|
||||
yy = (22.0-v)-11.0
|
||||
|
||||
yy = 11.0/(v/22.0)
|
||||
yy = 11.0/(22.0/v)
|
||||
yy = (v/22.0)/11.0
|
||||
yy = (22.0/v)/11.0
|
||||
A = calcIt(12345, 99)
|
||||
;A = 99/5 + lsb(12345)
|
||||
_vm_write_num(A)
|
||||
_vm_write_char($8d)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
sub printIt(length: XY, control: A) -> (A) {
|
||||
return 42 ; length / control
|
||||
sub calcIt(length: XY, control: A) -> (Y) {
|
||||
|
||||
return control/5 +lsb(length)
|
||||
}
|
||||
}
|
||||
|
@ -202,6 +202,11 @@ interface IAstProcessor {
|
||||
repeatLoop.statements = repeatLoop.statements.map { it.process(this) }
|
||||
return repeatLoop
|
||||
}
|
||||
|
||||
fun process(returnStmt: Return): IStatement {
|
||||
returnStmt.values = returnStmt.values.map { it.process(this) }
|
||||
return returnStmt
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -271,7 +276,7 @@ interface INameScope {
|
||||
fun subScopes() = statements.asSequence().filter { it is INameScope }.map { it as INameScope }.associate { it.name to it }
|
||||
|
||||
fun labelsAndVariables() = statements.asSequence().filter { it is Label || it is VarDecl }
|
||||
.associate {((it as? Label)?.name ?: (it as? VarDecl)?.name) to it }
|
||||
.associate {((it as? Label)?.name ?: (it as? VarDecl)?.name)!! to it }
|
||||
|
||||
fun lookup(scopedName: List<String>, statement: Node) : IStatement? {
|
||||
if(scopedName.size>1) {
|
||||
@ -504,10 +509,7 @@ class Return(var values: List<IExpression>, override val position: Position) : I
|
||||
values.forEach {it.linkParents(this)}
|
||||
}
|
||||
|
||||
override fun process(processor: IAstProcessor): IStatement {
|
||||
values = values.map { it.process(processor) }
|
||||
return this
|
||||
}
|
||||
override fun process(processor: IAstProcessor) = processor.process(this)
|
||||
|
||||
override fun toString(): String {
|
||||
return "Return(values: $values, pos=$position)"
|
||||
@ -1225,6 +1227,9 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
}
|
||||
TODO("return type for subroutine with multiple return values $stmt")
|
||||
}
|
||||
else if(stmt is Label) {
|
||||
return null
|
||||
}
|
||||
TODO("datatype of functioncall to $stmt")
|
||||
}
|
||||
|
||||
@ -1302,6 +1307,12 @@ data class SubroutineParameter(val name: String,
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
val type = when(register) {
|
||||
Register.A, Register.X, Register.Y -> DataType.BYTE
|
||||
Register.AX, Register.AY, Register.XY -> DataType.WORD
|
||||
null -> DataType.BYTE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1562,16 +1573,16 @@ private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
|
||||
}
|
||||
|
||||
|
||||
private fun prog8Parser.InlineasmContext.toAst(): IStatement =
|
||||
private fun prog8Parser.InlineasmContext.toAst() =
|
||||
InlineAssembly(INLINEASMBLOCK().text, toPosition())
|
||||
|
||||
|
||||
private fun prog8Parser.ReturnstmtContext.toAst() : IStatement {
|
||||
private fun prog8Parser.ReturnstmtContext.toAst() : Return {
|
||||
val values = expression_list()
|
||||
return Return(values?.toAst() ?: emptyList(), toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.UnconditionaljumpContext.toAst(): IStatement {
|
||||
private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
|
||||
|
||||
val address = integerliteral()?.toAst()?.number?.toInt()
|
||||
val identifier =
|
||||
|
@ -38,11 +38,6 @@ fun printWarning(msg: String, position: Position, detailInfo: String?=null) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* todo check subroutine call parameters against signature
|
||||
* todo check subroutine return values against the call's result assignments
|
||||
*/
|
||||
|
||||
class AstChecker(private val namespace: INameScope, private val compilerOptions: CompilationOptions) : IAstProcessor {
|
||||
private val checkResult: MutableList<AstException> = mutableListOf()
|
||||
|
||||
@ -62,12 +57,24 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
|
||||
// there must be a 'main' block with a 'start' subroutine for the program entry point.
|
||||
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" } as? Block?
|
||||
val startSub = mainBlock?.subScopes()?.get("start")
|
||||
val startSub = mainBlock?.subScopes()?.get("start") as? Subroutine
|
||||
if(startSub==null) {
|
||||
checkResult.add(SyntaxError("missing program entrypoint ('start' subroutine in 'main' block)", module.position))
|
||||
} else {
|
||||
if(startSub.parameters.isNotEmpty() || startSub.returnvalues.isNotEmpty())
|
||||
checkResult.add(SyntaxError("program entrypoint subroutine can't have parameters and/or return values", module.position))
|
||||
}
|
||||
}
|
||||
|
||||
override fun process(returnStmt: Return): IStatement {
|
||||
val expectedReturnValues = (returnStmt.definingScope() as? Subroutine)?.returnvalues ?: emptyList()
|
||||
if(expectedReturnValues.size != returnStmt.values.size)
|
||||
checkResult.add(SyntaxError("number of return values doesn't match subroutine return spec", returnStmt.position))
|
||||
|
||||
// @todo: check return value types versus sub return spec
|
||||
return super.process(returnStmt)
|
||||
}
|
||||
|
||||
override fun process(forLoop: ForLoop): IStatement {
|
||||
if(forLoop.body.isEmpty())
|
||||
printWarning("for loop body is empty", forLoop.position)
|
||||
@ -479,6 +486,12 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
val targetStatement = checkFunctionOrLabelExists(functionCall.target, stmtOfExpression)
|
||||
if(targetStatement!=null)
|
||||
checkBuiltinFunctionCall(functionCall, functionCall.position)
|
||||
|
||||
if(targetStatement is Label && functionCall.arglist.isNotEmpty())
|
||||
checkResult.add(SyntaxError("cannot use arguments when calling a label", functionCall.position))
|
||||
|
||||
// todo check subroutine call parameters against signature
|
||||
|
||||
return super.process(functionCall)
|
||||
}
|
||||
|
||||
@ -486,6 +499,12 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
val targetStatement = checkFunctionOrLabelExists(functionCall.target, functionCall)
|
||||
if(targetStatement!=null)
|
||||
checkBuiltinFunctionCall(functionCall, functionCall.position)
|
||||
|
||||
if(targetStatement is Label && functionCall.arglist.isNotEmpty())
|
||||
checkResult.add(SyntaxError("cannot use arguments when calling a label", functionCall.position))
|
||||
|
||||
// todo check subroutine call parameters against signature
|
||||
|
||||
return super.process(functionCall)
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import prog8.functions.BuiltinFunctionNames
|
||||
/**
|
||||
* Checks the validity of all identifiers (no conflicts)
|
||||
* Also builds a list of all (scoped) symbol definitions
|
||||
* 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 is set correctly.
|
||||
*/
|
||||
|
||||
@ -65,7 +66,7 @@ class AstIdentifiersChecker : IAstProcessor {
|
||||
// the builtin functions can't be redefined
|
||||
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
|
||||
} else {
|
||||
if(subroutine.parameters.any { BuiltinFunctionNames.contains(it.name) })
|
||||
if (subroutine.parameters.any { BuiltinFunctionNames.contains(it.name) })
|
||||
checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
|
||||
|
||||
val scopedName = subroutine.scopedname
|
||||
@ -75,6 +76,24 @@ class AstIdentifiersChecker : IAstProcessor {
|
||||
} else {
|
||||
symbols[scopedName] = subroutine
|
||||
}
|
||||
|
||||
// check that there are no local variables that redefine the subroutine's parameters
|
||||
val definedNames = subroutine.labelsAndVariables()
|
||||
val paramNames = subroutine.parameters.map { it.name }
|
||||
val definedNamesCorrespondingToParameters = definedNames.filter { paramNames.contains(it.key) }
|
||||
for(name in definedNamesCorrespondingToParameters) {
|
||||
if(name.value.position != subroutine.position)
|
||||
nameError(name.key, name.value.position, subroutine)
|
||||
}
|
||||
|
||||
// inject subroutine params as local variables (if they're not there yet)
|
||||
subroutine.parameters
|
||||
.filter { !definedNames.containsKey(it.name) }
|
||||
.forEach {
|
||||
val vardecl = VarDecl(VarDeclType.VAR, it.type, null, it.name, null, subroutine.position)
|
||||
vardecl.linkParents(subroutine)
|
||||
subroutine.statements.add(0, vardecl)
|
||||
}
|
||||
}
|
||||
return super.process(subroutine)
|
||||
}
|
||||
|
@ -311,17 +311,15 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
translateBinaryOperator(expr.operator)
|
||||
}
|
||||
is FunctionCall -> {
|
||||
expr.arglist.forEach { translate(it) }
|
||||
val target = expr.target.targetStatement(namespace)
|
||||
if(target is BuiltinFunctionStatementPlaceholder) {
|
||||
// call to a builtin function (some will just be an opcode!)
|
||||
expr.arglist.forEach { translate(it) }
|
||||
val funcname = expr.target.nameInSource[0]
|
||||
translateFunctionCall(funcname, expr.arglist)
|
||||
} else {
|
||||
when(target) {
|
||||
is Subroutine -> {
|
||||
stackvmProg.instr(Opcode.CALL, callLabel = target.scopedname)
|
||||
}
|
||||
is Subroutine -> translateSubroutineCall(target, expr.arglist, expr.parent)
|
||||
else -> TODO("non-builtin-function call to $target")
|
||||
}
|
||||
}
|
||||
@ -369,6 +367,26 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(stmt: FunctionCallStatement) {
|
||||
stackvmProg.line(stmt.position)
|
||||
val targetStmt = stmt.target.targetStatement(namespace)!!
|
||||
if(targetStmt is BuiltinFunctionStatementPlaceholder) {
|
||||
stmt.arglist.forEach { translate(it) }
|
||||
val funcname = stmt.target.nameInSource[0]
|
||||
translateFunctionCall(funcname, stmt.arglist)
|
||||
return
|
||||
}
|
||||
|
||||
when(targetStmt) {
|
||||
is Label ->
|
||||
stackvmProg.instr(Opcode.CALL, callLabel = targetStmt.scopedname)
|
||||
is Subroutine ->
|
||||
translateSubroutineCall(targetStmt, stmt.arglist, stmt)
|
||||
else ->
|
||||
throw AstException("invalid call target node type: ${targetStmt::class}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateFunctionCall(funcname: String, args: List<IExpression>) {
|
||||
// some functions are implemented as vm opcodes
|
||||
when (funcname) {
|
||||
@ -398,6 +416,22 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
}
|
||||
}
|
||||
|
||||
fun translateSubroutineCall(subroutine: Subroutine, arguments: List<IExpression>, parent: Node) {
|
||||
// setup the arguments: simply put them into the register vars
|
||||
// @todo support other types of parameters beside just registers
|
||||
for(param in arguments.zip(subroutine.parameters)) {
|
||||
val assign = Assignment(
|
||||
AssignTarget(param.second.register, null, param.first.position),
|
||||
null,
|
||||
param.first,
|
||||
param.first.position
|
||||
)
|
||||
assign.linkParents(parent)
|
||||
translate(assign)
|
||||
}
|
||||
stackvmProg.instr(Opcode.CALL, callLabel=subroutine.scopedname)
|
||||
}
|
||||
|
||||
private fun translateBinaryOperator(operator: String) {
|
||||
val opcode = when(operator) {
|
||||
"+" -> Opcode.ADD
|
||||
@ -435,25 +469,6 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
stackvmProg.instr(opcode)
|
||||
}
|
||||
|
||||
private fun translate(stmt: FunctionCallStatement) {
|
||||
stackvmProg.line(stmt.position)
|
||||
val targetStmt = stmt.target.targetStatement(namespace)!!
|
||||
if(targetStmt is BuiltinFunctionStatementPlaceholder) {
|
||||
stmt.arglist.forEach { translate(it) }
|
||||
val funcname = stmt.target.nameInSource[0]
|
||||
translateFunctionCall(funcname, stmt.arglist)
|
||||
return
|
||||
}
|
||||
|
||||
val targetname = when(targetStmt) {
|
||||
is Label -> targetStmt.scopedname
|
||||
is Subroutine -> targetStmt.scopedname
|
||||
else -> throw AstException("invalid call target node type: ${targetStmt::class}")
|
||||
}
|
||||
stmt.arglist.forEach { translate(it) }
|
||||
stackvmProg.instr(Opcode.CALL, callLabel = targetname)
|
||||
}
|
||||
|
||||
private fun createSyscall(funcname: String) {
|
||||
val function = (
|
||||
if (funcname.startsWith("_vm_"))
|
||||
@ -572,8 +587,18 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
}
|
||||
|
||||
private fun translate(stmt: Return) {
|
||||
if(stmt.values.isNotEmpty()) {
|
||||
TODO("return with value(s) not yet supported: $stmt")
|
||||
val returnvalues = (stmt.definingScope() as? Subroutine)?.returnvalues ?: emptyList()
|
||||
for(value in stmt.values.zip(returnvalues)) {
|
||||
// assign the return values to the proper result registers
|
||||
// @todo support other things than just result registers
|
||||
val assign = Assignment(
|
||||
AssignTarget(value.second.register, null, stmt.position),
|
||||
null,
|
||||
value.first,
|
||||
stmt.position
|
||||
)
|
||||
assign.linkParents(stmt.parent)
|
||||
translate(assign)
|
||||
}
|
||||
stackvmProg.line(stmt.position)
|
||||
stackvmProg.instr(Opcode.RETURN)
|
||||
|
@ -144,6 +144,13 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso
|
||||
// we chose to just print the blocks that aren't used.
|
||||
if(value is Block)
|
||||
println("${value.position} Info: block '$localname' is never used")
|
||||
if(value is VarDecl) {
|
||||
val scope = value.definingScope() as? Subroutine
|
||||
if(scope!=null && scope.parameters.any { it.name==localname})
|
||||
println("${value.position} Info: parameter '$localname' is never used")
|
||||
else
|
||||
println("${value.position} Info: variable '$localname' is never used")
|
||||
}
|
||||
parentScope.removeStatement(value)
|
||||
symbolsToRemove.add(name)
|
||||
optimizationsDone++
|
||||
|
@ -1390,7 +1390,8 @@ class StackVm(val traceOutputFile: String?) {
|
||||
val value = evalstack.pop()
|
||||
val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)")
|
||||
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
|
||||
if(variable.type!=value.type) throw VmExecutionException("value datatype ${value.type} is not the same as variable datatype ${variable.type}")
|
||||
if(variable.type!=value.type)
|
||||
throw VmExecutionException("value datatype ${value.type} is not the same as variable datatype ${variable.type} for var $varname")
|
||||
variables[varname] = value
|
||||
}
|
||||
Opcode.SHL_VAR -> {
|
||||
|
Loading…
Reference in New Issue
Block a user