mirror of
https://github.com/irmen/prog8.git
synced 2025-01-11 13:29:45 +00:00
expression datatype checks improvements
This commit is contained in:
parent
3228fa4c76
commit
ba81f32080
@ -24,7 +24,7 @@
|
||||
return
|
||||
}
|
||||
|
||||
sub thingy()->() {
|
||||
sub thingy()->(X) {
|
||||
;return 99
|
||||
return
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user