syntax checks on asmsubs

This commit is contained in:
Irmen de Jong 2018-10-08 21:57:36 +02:00
parent 9d10210466
commit 50464ebda1
6 changed files with 117 additions and 31 deletions

View File

@ -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

View File

@ -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<IStatement>,
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<SubroutineParameter>,
val returnvalues: List<DataType>,
val returntypes: List<DataType>,
val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<Register>,
val asmAddress: Int?,
val isAsmSubroutine: Boolean,
override var statements: MutableList<IStatement>,
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())
}

View File

@ -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<Register, Int>().withDefault { 0 }
val statusflagCounts = mutableMapOf<Statusflag, Int>().withDefault { 0 }
fun countRegisters(from: Iterable<RegisterOrStatusflag>) {
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)
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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 {