From 50464ebda1f3900b8d86225180d8b7fa35d4c767 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Mon, 8 Oct 2018 21:57:36 +0200 Subject: [PATCH] syntax checks on asmsubs --- compiler/src/build_the_compiler.sh | 9 ++- compiler/src/prog8/ast/AST.kt | 20 +++-- compiler/src/prog8/ast/AstChecker.kt | 92 ++++++++++++++++++++-- compiler/src/prog8/compiler/Compiler.kt | 2 +- compiler/src/prog8/parser/prog8Lexer.java | 11 ++- compiler/src/prog8/parser/prog8Parser.java | 14 ++-- 6 files changed, 117 insertions(+), 31 deletions(-) diff --git a/compiler/src/build_the_compiler.sh b/compiler/src/build_the_compiler.sh index bb4a7a0e3..481d74c30 100755 --- a/compiler/src/build_the_compiler.sh +++ b/compiler/src/build_the_compiler.sh @@ -1,8 +1,11 @@ #!/usr/bin/env bash + +find prog8 -name \*.java > javasources.txt mkdir -p compiled_java -javac -verbose -d compiled_java -cp ../antlr/lib/antlr-runtime-4.7.1.jar $(find . -name \*.java) -jar cf parser.jar -C compiled_java prog8 +javac -verbose -d compiled_java -cp ../antlr/lib/antlr-runtime-4.7.1.jar @javasources.txt +rm javasources.txt KOTLINC="bash ${HOME}/.IntelliJIdea2018.2/config/plugins/Kotlin/kotlinc/bin/kotlinc" +${KOTLINC} -verbose -include-runtime -d prog8_kotlin.jar -cp ../antlr/lib/antlr-runtime-4.7.1.jar:compiled_java prog8 -${KOTLINC} -verbose -include-runtime -d prog8_kotlin.jar -cp ../antlr/lib/antlr-runtime-4.7.1.jar:parser.jar prog8 +jar uf prog8_kotlin.jar -C compiled_java prog8 diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index facfa9437..190bbb600 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -1240,11 +1240,11 @@ class FunctionCall(override var target: IdentifierReference, return builtinFunctionReturnType(target.nameInSource[0], this.arglist, namespace, heap) } is Subroutine -> { - if(stmt.returnvalues.isEmpty()) + if(stmt.returntypes.isEmpty()) return null // no return value - if(stmt.returnvalues.size==1) - return stmt.returnvalues[0] - TODO("return type for subroutine with multiple return values $stmt") + if(stmt.returntypes.size==1) + return stmt.returntypes[0] + return null // has multiple return types... } is Label -> return null } @@ -1308,13 +1308,17 @@ class AnonymousScope(override var statements: MutableList, override fun process(processor: IAstProcessor) = processor.process(this) } + +// the subroutine class covers both the normal user-defined subroutines, +// and also the predefined/ROM/register-based subroutines. class Subroutine(override val name: String, val parameters: List, - val returnvalues: List, + val returntypes: List, val asmParameterRegisters: List, val asmReturnvaluesRegisters: List, val asmClobbers: Set, val asmAddress: Int?, + val isAsmSubroutine: Boolean, override var statements: MutableList, override val position: Position) : IStatement, INameScope { override lateinit var parent: Node @@ -1329,7 +1333,7 @@ class Subroutine(override val name: String, override fun process(processor: IAstProcessor) = processor.process(this) override fun toString(): String { - return "Subroutine(name=$name, parameters=$parameters, returnvalues=$returnvalues, ${statements.size} statements, address=$asmAddress)" + return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)" } } @@ -1578,7 +1582,8 @@ private fun prog8Parser.AsmsubroutineContext.toAst(): IStatement { val returnRegisters = returns.map { RegisterOrStatusflag(it.register, it.statusflag) } val clobbers = clobber()?.toAst() ?: emptySet() val statements = statement_block()?.toAst() ?: mutableListOf() - return Subroutine(name, normalParameters, normalReturnvalues, paramRegisters, returnRegisters, clobbers, address, statements, toPosition()) + return Subroutine(name, normalParameters, normalReturnvalues, + paramRegisters, returnRegisters, clobbers, address, true, statements, toPosition()) } private class AsmSubroutineParameter(name: String, @@ -1657,6 +1662,7 @@ private fun prog8Parser.SubroutineContext.toAst() : Subroutine { emptyList(), emptySet(), null, + false, statement_block()?.toAst() ?: mutableListOf(), toPosition()) } diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index b20e42f2c..eeaea5d7d 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -66,7 +66,7 @@ class AstChecker(private val namespace: INameScope, if(startSub==null) { checkResult.add(SyntaxError("missing program entrypoint ('start' subroutine in 'main' block)", module.position)) } else { - if(startSub.parameters.isNotEmpty() || startSub.returnvalues.isNotEmpty()) + if(startSub.parameters.isNotEmpty() || startSub.returntypes.isNotEmpty()) checkResult.add(SyntaxError("program entrypoint subroutine can't have parameters and/or return values", startSub.position)) } @@ -75,13 +75,13 @@ class AstChecker(private val namespace: INameScope, val irqBlock = module.statements.singleOrNull { it is Block && it.name=="irq" } as? Block? val irqSub = irqBlock?.subScopes()?.get("irq") as? Subroutine if(irqSub!=null) { - if(irqSub.parameters.isNotEmpty() || irqSub.returnvalues.isNotEmpty()) + if(irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty()) checkResult.add(SyntaxError("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position)) } } override fun process(returnStmt: Return): IStatement { - val expectedReturnValues = (returnStmt.definingScope() as? Subroutine)?.returnvalues ?: emptyList() + val expectedReturnValues = (returnStmt.definingScope() as? Subroutine)?.returntypes ?: emptyList() if(expectedReturnValues.size != returnStmt.values.size) { // if the return value is a function call, check the result of that call instead if(returnStmt.values.size==1 && returnStmt.values[0] is FunctionCall) { @@ -94,7 +94,7 @@ class AstChecker(private val namespace: INameScope, for (rv in expectedReturnValues.withIndex().zip(returnStmt.values)) { if(rv.first.value!=rv.second.resultingDatatype(namespace, heap)) - checkResult.add(ExpressionError("type of return value ${rv.first.index+1} doesn't match subroutine return type ${rv.first.value}", rv.second.position)) + checkResult.add(ExpressionError("type of return value #${rv.first.index+1} doesn't match subroutine return type ${rv.first.value}", rv.second.position)) } return super.process(returnStmt) } @@ -206,6 +206,10 @@ class AstChecker(private val namespace: INameScope, super.process(subroutine) + // user-defined subroutines can only have zero or one return type + if(!subroutine.isAsmSubroutine && subroutine.returntypes.size>1) + err("subroutine has more than one return value") + // subroutine must contain at least one 'return' or 'goto' // (or if it has an asm block, that must contain a 'rts' or 'jmp') if(subroutine.statements.count { it is Return || it is Jump } == 0) { @@ -215,7 +219,7 @@ class AstChecker(private val namespace: INameScope, .map { (it as InlineAssembly).assembly } .count { "rts" in it || "\trts" in it || "jmp" in it || "\tjmp" in it } if (amount == 0) { - if(subroutine.returnvalues.isNotEmpty()) { + if(subroutine.returntypes.isNotEmpty()) { // for asm subroutines with an address, no statement check is possible. if(subroutine.asmAddress==null) err("subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or 'rts' / 'jmp' in case of %asm)") @@ -231,6 +235,77 @@ class AstChecker(private val namespace: INameScope, err("subroutines can only be defined in the scope of a block or within another subroutine") } + if(subroutine.isAsmSubroutine) { + if(subroutine.asmParameterRegisters.size != subroutine.parameters.size) + err("number of asm parameter registers is not the same as number of parameters") + if(subroutine.asmReturnvaluesRegisters.size != subroutine.returntypes.size) + err("number of return registers is not the same as number of return values") + for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) { + if(param.second.register==Register.A || param.second.register==Register.X || + param.second.register==Register.Y || param.second.statusflag!=null) { + if(param.first.type!=DataType.BYTE) + err("parameter '${param.first.name}' should be byte") + } + if(param.second.register==Register.AX || param.second.register==Register.AY || + param.second.register==Register.XY) { + if(param.first.type==DataType.BYTE || param.first.type==DataType.FLOAT) + err("parameter '${param.first.name}' should be word/str/array") + } + } + for(ret in subroutine.returntypes.withIndex().zip(subroutine.asmReturnvaluesRegisters)) { + if(ret.second.register==Register.A || ret.second.register==Register.X || + ret.second.register==Register.Y || ret.second.statusflag!=null) { + if(ret.first.value!=DataType.BYTE) + err("return value #${ret.first.index+1} should be byte") + } + if(ret.second.register==Register.AX || ret.second.register==Register.AY || + ret.second.register==Register.XY) { + if(ret.first.value==DataType.BYTE || ret.first.value==DataType.FLOAT) + err("return value #${ret.first.index+1} should be byte") + } + } + + val regCounts = mutableMapOf().withDefault { 0 } + val statusflagCounts = mutableMapOf().withDefault { 0 } + fun countRegisters(from: Iterable) { + regCounts.clear() + statusflagCounts.clear() + for(p in from) { + if (p.register != null) { + when(p.register) { + Register.A, Register.X, Register.Y -> regCounts[p.register] = regCounts.getValue(p.register) + 1 + Register.AX -> { + regCounts[Register.A] = regCounts.getValue(Register.A) + 1 + regCounts[Register.X] = regCounts.getValue(Register.X) + 1 + } + Register.AY -> { + regCounts[Register.A] = regCounts.getValue(Register.A) + 1 + regCounts[Register.Y] = regCounts.getValue(Register.Y) + 1 + } + Register.XY -> { + regCounts[Register.X] = regCounts.getValue(Register.X) + 1 + regCounts[Register.Y] = regCounts.getValue(Register.Y) + 1 + } + } + } + else if(p.statusflag!=null) + statusflagCounts[p.statusflag] = statusflagCounts.getValue(p.statusflag) + 1 + } + } + countRegisters(subroutine.asmParameterRegisters) + if(regCounts.any{it.value>1}) + err("a register is used multiple times in the parameters") + if(statusflagCounts.any{it.value>1}) + err("a status flag is used multiple times in the parameters") + countRegisters(subroutine.asmReturnvaluesRegisters) + if(regCounts.any{it.value>1}) + err("a register is used multiple times in the return values") + if(statusflagCounts.any{it.value>1}) + err("a status flag is used multiple times in the return values") + + if(subroutine.asmClobbers.intersect(regCounts.keys).isNotEmpty()) + err("a return register is also in the clobber list") + } return subroutine } @@ -299,7 +374,7 @@ class AstChecker(private val namespace: INameScope, val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace, heap) if(sourceDatatype==null) { if(assignment.value is FunctionCall) - checkResult.add(ExpressionError("function call doesn't return a value to use in assignment", assignment.value.position)) + checkResult.add(ExpressionError("function call doesn't return a suitable value to use in assignment", assignment.value.position)) else checkResult.add(ExpressionError("assignment value is invalid or has no proper datatype", assignment.value.position)) } @@ -529,8 +604,9 @@ class AstChecker(private val namespace: INameScope, val targetStatement = checkFunctionOrLabelExists(functionCall.target, functionCall) if(targetStatement!=null) checkFunctionCall(targetStatement, functionCall.arglist, functionCall.position) - if(targetStatement is Subroutine && targetStatement.returnvalues.isNotEmpty()) - printWarning("result value of subroutine call is discarded", functionCall.position) + if(targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty()) + if(!targetStatement.isAsmSubroutine) + printWarning("result value of subroutine call is discarded", functionCall.position) return super.process(functionCall) } diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index cbd2e4ada..5c98e6159 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -866,7 +866,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, is Subroutine -> { translateSubroutineCall(targetStmt, stmt.arglist, stmt.position) // make sure we clean up the unused result values from the stack. - for(rv in targetStmt.returnvalues) { + for(rv in targetStmt.returntypes) { val opcode=opcodeDiscard(rv) stackvmProg.instr(opcode) } diff --git a/compiler/src/prog8/parser/prog8Lexer.java b/compiler/src/prog8/parser/prog8Lexer.java index 2df8649ff..e91c2ec5d 100644 --- a/compiler/src/prog8/parser/prog8Lexer.java +++ b/compiler/src/prog8/parser/prog8Lexer.java @@ -1,13 +1,12 @@ // Generated from /home/irmen/Projects/prog8/compiler/antlr/prog8.g4 by ANTLR 4.7 package prog8.parser; -import org.antlr.v4.runtime.Lexer; -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.TokenStream; + import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.*; +import org.antlr.v4.runtime.atn.ATN; +import org.antlr.v4.runtime.atn.ATNDeserializer; +import org.antlr.v4.runtime.atn.LexerATNSimulator; +import org.antlr.v4.runtime.atn.PredictionContextCache; import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.misc.*; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) public class prog8Lexer extends Lexer { diff --git a/compiler/src/prog8/parser/prog8Parser.java b/compiler/src/prog8/parser/prog8Parser.java index eb0b833ef..ec5434832 100644 --- a/compiler/src/prog8/parser/prog8Parser.java +++ b/compiler/src/prog8/parser/prog8Parser.java @@ -1,13 +1,15 @@ // Generated from /home/irmen/Projects/prog8/compiler/antlr/prog8.g4 by ANTLR 4.7 package prog8.parser; -import org.antlr.v4.runtime.atn.*; -import org.antlr.v4.runtime.dfa.DFA; + import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.misc.*; -import org.antlr.v4.runtime.tree.*; +import org.antlr.v4.runtime.atn.ATN; +import org.antlr.v4.runtime.atn.ATNDeserializer; +import org.antlr.v4.runtime.atn.ParserATNSimulator; +import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.tree.TerminalNode; + import java.util.List; -import java.util.Iterator; -import java.util.ArrayList; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) public class prog8Parser extends Parser {