expression datatype checks improvements

This commit is contained in:
Irmen de Jong 2018-09-14 19:38:22 +02:00
parent 3228fa4c76
commit ba81f32080
6 changed files with 172 additions and 71 deletions

View File

@ -24,7 +24,7 @@
return
}
sub thingy()->() {
sub thingy()->(X) {
;return 99
return
}

View File

@ -101,17 +101,26 @@
byte equalQQ = 4==4
const byte equalQQ2 = (4+hopla)>0
const str string1 = "hallo"
str string2 = "doei"
equalQQ++
AX++
A=X + round(sin(Y))
A=msb(X + round(sin(Y)+ 1111))
A=lsb(X + round(sin(Y)+1111))
equalQQ= X
equalQQ= len([X, Y, AX])
equalQQ= len("abcdef")
equalQQ= len([1,2,3])
equalQQ= len(string1)
equalQQ= len(string2) ; @todo len function call on str type ALSO ADD ALL THIS TO DOCS OF FUNCTION LEN!
P_carry(1)
P_irqd(0)
equalQQ = foo(33)
equalQQ = main.foo(33)
;equalQQ = foo(33)
;equalQQ = main.foo(33)
foo(33)
main.foo(33)
XY = hopla*2+hopla1
byte equalWW = 4==4
@ -130,11 +139,11 @@
}
if(6==6) {
A=sin(X)
X=max([1,2,Y])
X=min([1,2,Y])
A=sin(X) ; @todo should give error of float loss of precision
X=max([1,2,Y]) ; @todo must be byte so should be ok
X=min([1,2,Y]) ; @todo must be byte so should be ok
X=lsl(12)
X=lsl(Y)
X=lsl(Y) ; @todo must be byte so should be ok
P_carry(0)
P_carry(1)
P_carry(1-1)
@ -159,10 +168,8 @@
word blerp1 =999
word blerp3 = 1
byte blerp2 =99
float flob =1.1
dinges=blerp1
A=blerp1 ; @todo error can't assign word to byte
A=blerp3 ; @todo error can't assign word to byte
blerp2=blerp3; @todo error can't assign word to byte
blerp3 = blerp2
A=blerp2
A=$02
@ -172,7 +179,6 @@
A=%0010011001
A=99.w
XY=blerp1
X=blerp1 ; @todo error can't assign word to byte
X=blerp2
return
}

View File

@ -1,29 +0,0 @@
%output prg
%launcher basic
~ main {
; memory byte derp = max([$ffdd]) ; @todo implement memory vars in stackvm
; memory byte derpA = abs(-20000)
; memory byte derpB = max([1, 2.2, 4.4, 100])
; memory byte cderp = min([$ffdd])+ (1/1)
; memory byte cderpA = min([$ffdd, 10, 20, 30])
; memory byte cderpB = min([1, 2.2, 4.4, 100])
; memory byte derp2 = 2+$ffdd+round(10*sin(3.1))
sub start() -> () {
P_irqd(1) ; is okay. (has side-effects, is not a pure function)
word dinges = 0
word blerp1 =999
word blerp3 = 1
byte blerp2 =99
dinges=blerp1
blerp3 = blerp2
A=blerp2
A=X
XY=X
XY=AX
return
}
}

View File

@ -5,6 +5,7 @@ import il65.parser.il65Parser
import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.tree.TerminalNode
import java.nio.file.Paths
import javax.xml.crypto.Data
import kotlin.math.floor
@ -630,7 +631,7 @@ interface IExpression: Node {
fun constValue(namespace: INameScope): LiteralValue?
fun process(processor: IAstProcessor): IExpression
fun referencesIdentifier(name: String): Boolean
// TODO fun resultingDatatype(): DataType
fun resultingDatatype(namespace: INameScope): DataType?
}
@ -648,6 +649,7 @@ class PrefixExpression(val operator: String, var expression: IExpression) : IExp
override fun constValue(namespace: INameScope): LiteralValue? = null
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name)
override fun resultingDatatype(namespace: INameScope): DataType? = expression.resultingDatatype(namespace)
}
@ -666,6 +668,45 @@ class BinaryExpression(var left: IExpression, val operator: String, var right: I
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String) = left.referencesIdentifier(name) || right.referencesIdentifier(name)
override fun resultingDatatype(namespace: INameScope): DataType? {
val leftDt = left.resultingDatatype(namespace)
val rightDt = right.resultingDatatype(namespace)
return when(operator) {
"+", "-", "*", "/", "**" -> if(leftDt==null || rightDt==null) null else arithmeticOpDt(leftDt, rightDt)
"&" -> leftDt
"|" -> leftDt
"^" -> leftDt
"and", "or", "xor",
"<", ">",
"<=", ">=",
"==", "!=" -> DataType.BYTE
else -> throw FatalAstException("resulting datatype check for invalid operator $operator")
}
}
private fun arithmeticOpDt(leftDt: DataType, rightDt: DataType): DataType {
return when(leftDt) {
DataType.BYTE -> when(rightDt) {
DataType.BYTE -> DataType.BYTE
DataType.WORD -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.WORD -> when(rightDt) {
DataType.BYTE -> DataType.BYTE
DataType.WORD -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.FLOAT -> when(rightDt) {
DataType.BYTE -> DataType.FLOAT
DataType.WORD -> DataType.FLOAT
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
}
}
private data class ByteOrWordLiteral(val intvalue: Int, val datatype: DataType) {
@ -742,6 +783,17 @@ data class LiteralValue(val bytevalue: Short? = null,
override fun toString(): String {
return "LiteralValue(byte=$bytevalue, word=$wordvalue, float=$floatvalue, str=$strvalue, array=$arrayvalue pos=$position)"
}
override fun resultingDatatype(namespace: INameScope): DataType? {
return when {
isByte -> DataType.BYTE
isWord -> DataType.WORD
isFloat -> DataType.FLOAT
isString -> DataType.STR // @todo which string?
isArray -> DataType.ARRAY // @todo byte/word array?
else -> throw FatalAstException("literalvalue has no value")
}
}
}
@ -758,6 +810,19 @@ class RangeExpr(var from: IExpression, var to: IExpression) : IExpression {
override fun constValue(namespace: INameScope): LiteralValue? = null
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String): Boolean = from.referencesIdentifier(name) || to.referencesIdentifier(name)
override fun resultingDatatype(namespace: INameScope): DataType? {
val fromDt=from.resultingDatatype(namespace)
val toDt=to.resultingDatatype(namespace)
return when {
fromDt==null || toDt==null -> null
fromDt==DataType.WORD || toDt==DataType.WORD -> DataType.WORD
fromDt==DataType.STR || toDt==DataType.STR -> DataType.STR
fromDt==DataType.STR_P || toDt==DataType.STR_P -> DataType.STR_P
fromDt==DataType.STR_S || toDt==DataType.STR_S -> DataType.STR_S
fromDt==DataType.STR_PS || toDt==DataType.STR_PS -> DataType.STR_PS
else -> DataType.BYTE
}
}
}
@ -776,6 +841,13 @@ class RegisterExpr(val register: Register) : IExpression {
override fun toString(): String {
return "RegisterExpr(register=$register, pos=$position)"
}
override fun resultingDatatype(namespace: INameScope): DataType? {
return when(register){
Register.A, Register.X, Register.Y -> DataType.BYTE
Register.AX, Register.AY, Register.XY -> DataType.WORD
}
}
}
@ -811,6 +883,15 @@ data class IdentifierReference(val nameInSource: List<String>) : IExpression {
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String): Boolean = nameInSource.last() == name // @todo is this correct all the time?
override fun resultingDatatype(namespace: INameScope): DataType? {
val targetStmt = targetStatement(namespace)
if(targetStmt is VarDecl) {
return targetStmt.datatype
} else {
throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position")
}
}
}
@ -858,11 +939,13 @@ class FunctionCall(override var target: IdentifierReference, override var arglis
arglist.forEach { it.linkParents(this) }
}
override fun constValue(namespace: INameScope): LiteralValue? {
override fun constValue(namespace: INameScope) = constValue(namespace, true)
private fun constValue(namespace: INameScope, withDatatypeCheck: Boolean): LiteralValue? {
// if the function is a built-in function and the args are consts, should try to const-evaluate!
if(target.nameInSource.size>1) return null
try {
return when (target.nameInSource[0]) {
val resultValue = when (target.nameInSource[0]) {
"sin" -> builtinSin(arglist, position, namespace)
"cos" -> builtinCos(arglist, position, namespace)
"abs" -> builtinAbs(arglist, position, namespace)
@ -897,6 +980,23 @@ class FunctionCall(override var target: IdentifierReference, override var arglis
"P_irqd" -> throw ExpressionError("builtin function P_irqd can't be used in expressions because it doesn't return a value", position)
else -> null
}
if(withDatatypeCheck) {
val resultDt = this.resultingDatatype(namespace)
if(resultValue==null)
return resultValue
when(resultDt) {
DataType.BYTE -> if(resultValue.isByte) return resultValue
DataType.WORD -> if(resultValue.isWord) return resultValue
DataType.FLOAT -> if(resultValue.isFloat) return resultValue
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> if(resultValue.isString) return resultValue
DataType.ARRAY -> if(resultValue.isArray) return resultValue
DataType.ARRAY_W -> if(resultValue.isArray) return resultValue
DataType.MATRIX -> TODO("expected matrix as constvalue this is not yet supported")
}
throw FatalAstException("evaluated const expression result value doesn't match expected datatype $resultDt, pos=$position")
} else {
return resultValue
}
}
catch(x: NotConstArgumentException) {
// const-evaluating the builtin function call failed.
@ -910,6 +1010,34 @@ class FunctionCall(override var target: IdentifierReference, override var arglis
override fun process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String): Boolean = target.referencesIdentifier(name) || arglist.any{it.referencesIdentifier(name)}
override fun resultingDatatype(namespace: INameScope): DataType? {
val constVal = constValue(namespace, false)
if(constVal!=null)
return constVal.resultingDatatype(namespace)
val stmt = target.targetStatement(namespace)
if(stmt is BuiltinFunctionStatementPlaceholder) {
if(target.nameInSource[0] == "P_carry" || target.nameInSource[0]=="P_irqd") {
return null // these have no return value
}
return DataType.BYTE // @todo table lookup to determine result type of builtin function call
}
else if(stmt is Subroutine) {
if(stmt.returnvalues.isEmpty()) {
return null // no return value
}
if(stmt.returnvalues.size==1) {
return when(stmt.returnvalues[0].register) {
Register.A, Register.X, Register.Y -> DataType.BYTE
Register.AX, Register.AY, Register.XY -> DataType.WORD
else -> TODO("return type for non-register result from subroutine $stmt")
}
}
TODO("return type for subroutine with multiple return values $stmt")
}
TODO("datatype of functioncall to $stmt")
}
}

View File

@ -165,24 +165,16 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
if(constVal!=null) {
checkValueTypeAndRange(targetDatatype, null, assignment.value as LiteralValue, assignment.position)
} else {
val sourceDatatype: DataType = when(assignment.value) {
is RegisterExpr -> {
when((assignment.value as RegisterExpr).register) {
Register.A, Register.X, Register.Y -> DataType.BYTE
Register.AX, Register.AY, Register.XY -> DataType.WORD
}
}
is IdentifierReference -> {
val targetStmt = (assignment.value as IdentifierReference).targetStatement(namespace)
if(targetStmt is VarDecl) {
targetStmt.datatype
} else {
throw FatalAstException("cannot get datatype from assignment value ${assignment.value}, pos=${assignment.position}")
}
}
else -> TODO("check assignment compatibility for value ${assignment.value}, pos=${assignment.position}")
val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace)
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))
else
checkResult.add(ExpressionError("assignment source ${assignment.value} is no value or has no proper datatype", assignment.value.position))
}
else {
checkAssignmentCompatible(targetDatatype, sourceDatatype, assignment.position)
}
checkAssignmentCompatible(targetDatatype, sourceDatatype, assignment.position)
}
return super.process(assignment)
@ -539,7 +531,13 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
if(result)
return true
checkResult.add(ExpressionError("cannot assign ${sourceDatatype.toString().toLowerCase()} to ${targetDatatype.toString().toLowerCase()}", position))
if(sourceDatatype==DataType.WORD && targetDatatype==DataType.BYTE)
checkResult.add(ExpressionError("cannot assign word to byte, use msb() or lsb()?", position))
else if(sourceDatatype==DataType.FLOAT && (targetDatatype==DataType.BYTE || targetDatatype==DataType.WORD))
checkResult.add(ExpressionError("cannot assign ${sourceDatatype.toString().toLowerCase()} to ${targetDatatype.toString().toLowerCase()}; possible loss of precision", position))
else
checkResult.add(ExpressionError("cannot assign ${sourceDatatype.toString().toLowerCase()} to ${targetDatatype.toString().toLowerCase()}", position))
return false
}
}

View File

@ -172,15 +172,13 @@ fun builtinAvg(args: List<IExpression>, position: Position?, namespace:INameScop
fun builtinLen(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue {
if(args.size!=1)
throw SyntaxError("len requires one non-scalar argument", position)
val iterable = args[0].constValue(namespace)
if(iterable?.arrayvalue == null)
throw SyntaxError("len requires one non-scalar argument", position)
val constants = iterable.arrayvalue.map { it.constValue(namespace)?.asNumericValue }
if(constants.contains(null))
throw NotConstArgumentException()
val result = (constants.map { it!!.toDouble() }).size
return numericLiteral(result, args[0].position)
throw SyntaxError("len requires one argument", position)
val argument = args[0].constValue(namespace) ?: throw NotConstArgumentException()
return when {
argument.isArray -> numericLiteral(argument.arrayvalue!!.size, args[0].position)
argument.isString -> numericLiteral(argument.strvalue!!.length, args[0].position)
else -> throw FatalAstException("len of weird argument ${args[0]}")
}
}
fun builtinAny(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue