diff --git a/compiler/examples/test.p8 b/compiler/examples/test.p8 index d3c61370f..2f600bc10 100644 --- a/compiler/examples/test.p8 +++ b/compiler/examples/test.p8 @@ -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) } } diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index 9407272f5..6556279c5 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -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, statement: Node) : IStatement? { if(scopedName.size>1) { @@ -504,10 +509,7 @@ class Return(var values: List, 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 = diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index 923fadc0d..916074d2d 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -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 = 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) } diff --git a/compiler/src/prog8/ast/AstIdentifiersChecker.kt b/compiler/src/prog8/ast/AstIdentifiersChecker.kt index e5ad0bfb8..4f2c2e233 100644 --- a/compiler/src/prog8/ast/AstIdentifiersChecker.kt +++ b/compiler/src/prog8/ast/AstIdentifiersChecker.kt @@ -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) } diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 6b295d578..bf4672f56 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -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) { // 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, 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) diff --git a/compiler/src/prog8/optimizing/StatementOptimizer.kt b/compiler/src/prog8/optimizing/StatementOptimizer.kt index 94117e871..97b04dd66 100644 --- a/compiler/src/prog8/optimizing/StatementOptimizer.kt +++ b/compiler/src/prog8/optimizing/StatementOptimizer.kt @@ -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++ diff --git a/compiler/src/prog8/stackvm/StackVm.kt b/compiler/src/prog8/stackvm/StackVm.kt index 50e037156..a7301046b 100644 --- a/compiler/src/prog8/stackvm/StackVm.kt +++ b/compiler/src/prog8/stackvm/StackVm.kt @@ -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 -> {