Files
prog8/compilerAst/src/prog8/ast/expressions/AstExpressions.kt
Irmen de Jong f70341df1b fix ptr errors
2025-05-19 20:06:31 +02:00

1736 lines
79 KiB
Kotlin

package prog8.ast.expressions
import prog8.ast.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.core.*
import prog8.code.target.encodings.JapaneseCharacterConverter
import java.io.CharConversionException
import java.util.*
import kotlin.math.abs
import kotlin.math.floor
import kotlin.math.truncate
sealed class Expression: Node {
abstract override fun copy(): Expression
abstract fun constValue(program: Program): NumericLiteral?
abstract fun accept(visitor: IAstVisitor)
abstract fun accept(visitor: AstWalker, parent: Node)
abstract fun inferType(program: Program): InferredTypes.InferredType
abstract val isSimple: Boolean
infix fun isSameAs(assigntarget: AssignTarget) = assigntarget.isSameAs(this)
infix fun isSameAs(other: Expression): Boolean {
if(this===other)
return true
return when(this) {
is IdentifierReference ->
(other is IdentifierReference && other.nameInSource==nameInSource)
is PrefixExpression ->
(other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
is BinaryExpression -> {
if(other !is BinaryExpression || other.operator!=operator)
false
else if(operator in AssociativeOperators)
(other.left isSameAs left && other.right isSameAs right) || (other.left isSameAs right && other.right isSameAs left)
else
other.left isSameAs left && other.right isSameAs right
}
is ArrayIndexedExpression -> {
(other is ArrayIndexedExpression && other.arrayvar.nameInSource == arrayvar.nameInSource
&& other.indexer isSameAs indexer)
}
is DirectMemoryRead -> {
(other is DirectMemoryRead && other.addressExpression isSameAs addressExpression)
}
is TypecastExpression -> {
(other is TypecastExpression && other.implicit==implicit && other.type==type && other.expression isSameAs expression)
}
is AddressOf -> {
(other is AddressOf && other.identifier?.nameInSource == identifier?.nameInSource && other.arrayIndex==arrayIndex && other.dereference==dereference)
}
is RangeExpression -> {
(other is RangeExpression && other.from==from && other.to==to && other.step==step)
}
is FunctionCallExpression -> {
(other is FunctionCallExpression && other.target.nameInSource == target.nameInSource
&& other.args.size == args.size
&& other.args.zip(args).all { it.first isSameAs it.second } )
}
else -> other==this
}
}
fun typecastTo(targetDt: BaseDataType, sourceDt: DataType, implicit: Boolean=false): Pair<Boolean, Expression> {
require(!sourceDt.isUndefined && targetDt!=BaseDataType.UNDEFINED)
if(sourceDt.base==targetDt && sourceDt.sub==null)
return Pair(false, this)
if(this is TypecastExpression) {
this.type = DataType.forDt(targetDt)
return Pair(false, this)
}
val typecast = TypecastExpression(this, DataType.forDt(targetDt), implicit, this.position)
return Pair(true, typecast)
}
}
class PrefixExpression(val operator: String, var expression: Expression, override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
expression.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(node === expression && replacement is Expression)
expression = replacement
replacement.parent = this
}
override fun copy() = PrefixExpression(operator, expression.copy(), position)
override fun constValue(program: Program): NumericLiteral? {
val constval = expression.constValue(program) ?: return null
val converted = when(operator) {
"+" -> constval
"-" -> when {
constval.type.isInteger -> NumericLiteral.optimalInteger(-constval.number.toInt(), constval.position)
constval.type == BaseDataType.FLOAT -> NumericLiteral(BaseDataType.FLOAT, -constval.number, constval.position)
else -> throw ExpressionError("can only take negative of int or float", constval.position)
}
"~" -> when (constval.type) {
BaseDataType.BYTE -> NumericLiteral(BaseDataType.BYTE, constval.number.toInt().inv().toDouble(), constval.position)
BaseDataType.UBYTE -> NumericLiteral(BaseDataType.UBYTE, (constval.number.toInt().inv() and 255).toDouble(), constval.position)
BaseDataType.WORD -> NumericLiteral(BaseDataType.WORD, constval.number.toInt().inv().toDouble(), constval.position)
BaseDataType.UWORD -> NumericLiteral(BaseDataType.UWORD, (constval.number.toInt().inv() and 65535).toDouble(), constval.position)
BaseDataType.LONG -> NumericLiteral(BaseDataType.LONG, constval.number.toInt().inv().toDouble(), constval.position)
else -> throw ExpressionError("can only take bitwise inversion of int", constval.position)
}
"not" -> NumericLiteral.fromBoolean(constval.number==0.0, constval.position)
else -> throw FatalAstException("invalid operator")
}
converted.linkParents(this.parent)
return converted
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>) = expression.referencesIdentifier(nameInSource)
override fun inferType(program: Program): InferredTypes.InferredType {
val inferred = expression.inferType(program)
return when(operator) {
"+" -> inferred
"not" -> InferredTypes.knownFor(BaseDataType.BOOL)
"~" -> {
if(inferred.isBytes) InferredTypes.knownFor(BaseDataType.UBYTE)
else if(inferred.isWords) InferredTypes.knownFor(BaseDataType.UWORD)
else InferredTypes.unknown()
}
"-" -> {
if(inferred.isBytes) InferredTypes.knownFor(BaseDataType.BYTE)
else if(inferred.isWords) InferredTypes.knownFor(BaseDataType.WORD)
else inferred
}
else -> throw FatalAstException("weird prefix expression operator")
}
}
override val isSimple = expression.isSimple
override fun toString(): String {
return "Prefix($operator $expression)"
}
}
class BinaryExpression(
var left: Expression,
var operator: String,
var right: Expression,
override val position: Position,
private val insideParentheses: Boolean = false
) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
left.linkParents(this)
right.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
when {
node===left -> left = replacement
node===right -> right = replacement
else -> throw FatalAstException("invalid replace, no child $node")
}
replacement.parent = this
}
override fun copy() = BinaryExpression(left.copy(), operator, right.copy(), position, insideParentheses)
override fun toString() = "[$left $operator $right]"
override val isSimple = false
// binary expression should actually have been optimized away into a single value, before const value was requested...
override fun constValue(program: Program): NumericLiteral? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>) = left.referencesIdentifier(nameInSource) || right.referencesIdentifier(nameInSource)
override fun inferType(program: Program): InferredTypes.InferredType {
val leftDt = left.inferType(program)
val rightDt = right.inferType(program)
return when (operator) {
"+", "-", "*", "%", "/" -> {
if (!leftDt.isKnown || !rightDt.isKnown)
InferredTypes.unknown()
else {
try {
val dt = InferredTypes.knownFor(
commonDatatype(
leftDt.getOr(DataType.BYTE),
rightDt.getOr(DataType.BYTE),
null, null
).first
)
if(operator=="*") {
// if both operands are the same, X*X is always positive.
if(left isSameAs right) {
if(dt istype DataType.BYTE)
InferredTypes.knownFor(BaseDataType.UBYTE)
else if(dt istype DataType.WORD)
InferredTypes.knownFor(BaseDataType.UWORD)
else
dt
} else
dt
} else
dt
} catch (_: FatalAstException) {
InferredTypes.unknown()
}
}
}
"&", "|", "^" -> when(leftDt.getOrUndef()) {
DataType.BYTE -> InferredTypes.knownFor(BaseDataType.UBYTE)
DataType.WORD -> InferredTypes.knownFor(BaseDataType.UWORD)
DataType.BOOL -> InferredTypes.knownFor(BaseDataType.UBYTE)
else -> leftDt
}
"and", "or", "xor", "not", "in", "not in",
"<", ">",
"<=", ">=",
"==", "!=" -> InferredTypes.knownFor(BaseDataType.BOOL)
"<<", ">>" -> leftDt
"." -> {
val leftExpr = left as? BinaryExpression
val leftIdentfier = left as? IdentifierReference
val leftIndexer = left as? ArrayIndexedExpression
val rightIdentifier = right as? IdentifierReference
val rightIndexer = right as? ArrayIndexedExpression
if(rightIdentifier!=null) {
val struct: StructDecl? =
if (leftIdentfier != null) {
// PTR . FIELD
leftIdentfier.targetVarDecl()?.datatype?.subType as? StructDecl
} else if(leftIndexer!=null) {
// ARRAY[x].NAME --> maybe it's a pointer dereference
leftIndexer.arrayvar.targetVarDecl()?.datatype?.subType as? StructDecl
} else if(leftExpr!=null) {
// SOMEEXPRESSION . NAME
val leftDt = leftExpr.inferType(program)
if(leftDt.isPointer)
leftDt.getOrUndef().subType as StructDecl?
else
null
}
else null
if (struct == null)
InferredTypes.unknown()
else {
val fieldDt = if(rightIdentifier.nameInSource.size==1)
struct.getFieldType(rightIdentifier.nameInSource.single())
else
rightIdentifier.traverseDerefChainForDt(struct)
if (fieldDt != null)
if(fieldDt.isUndefined) InferredTypes.unknown() else InferredTypes.knownFor(fieldDt)
else
InferredTypes.unknown()
}
} else if(rightIndexer!=null) {
if(leftDt.isStructInstance) {
// pointer[x].field[y] --> type is the dt of 'field'
var fieldDt = (leftDt.getOrUndef().subType as? StructDecl)?.getFieldType(rightIndexer.arrayvar.nameInSource.single())
if (fieldDt == null)
InferredTypes.unknown()
else {
val struct = fieldDt.subType as StructDecl
fieldDt = struct.getFieldType(rightIndexer.arrayvar.nameInSource.single())
if(fieldDt!=null)
if(fieldDt.isUndefined) InferredTypes.unknown() else InferredTypes.knownFor(fieldDt)
else
InferredTypes.unknown()
}
} else
InferredTypes.unknown() // TODO("something.field[x] at ${right.position}")
// TODO I don't think we can evaluate this type because it could end up in as a struct instance, which we don't support yet... rewrite or just give an error?
} else
InferredTypes.unknown()
}
else -> throw FatalAstException("resulting datatype check for invalid operator $operator")
}
}
companion object {
fun commonDatatype(leftDt: DataType, rightDt: DataType,
left: Expression?, right: Expression?): Pair<DataType, Expression?> {
if(leftDt.isUndefined || rightDt.isUndefined)
return DataType.UNDEFINED to null
// anything combined with a pointer type -> pointer type.
if(leftDt.isPointer)
return leftDt to null
if(rightDt.isPointer)
return rightDt to null
// byte + byte -> byte
// byte + word -> word
// word + byte -> word
// word + word -> word
// a combination with a float will be float (but give a warning about this!)
// if left or right is a numeric literal, and its value fits in the type of the other operand, use the other's operand type
// EXCEPTION: if the numeric value is a word and the other operand is a byte type (to allow v * $0008 for example)
if (left is NumericLiteral && rightDt.isNumericOrBool) {
if(!(leftDt.isWord && rightDt.isByte)) {
val optimal = NumericLiteral.optimalNumeric(rightDt.base, null, left.number, left.position)
if (optimal.type != leftDt.base && DataType.forDt(optimal.type) isAssignableTo rightDt) {
return DataType.forDt(optimal.type) to left
}
}
}
if (right is NumericLiteral && leftDt.isNumericOrBool) {
if(!(rightDt.isWord && leftDt.isByte)) {
val optimal = NumericLiteral.optimalNumeric(leftDt.base, null, right.number, right.position)
if (optimal.type != rightDt.base && DataType.forDt(optimal.type) isAssignableTo leftDt) {
return DataType.forDt(optimal.type) to right
}
}
}
return when (leftDt.base) {
BaseDataType.BOOL -> {
return if(rightDt.isBool)
Pair(DataType.BOOL, null)
else
Pair(DataType.BOOL, right)
}
BaseDataType.UBYTE -> {
when (rightDt.base) {
BaseDataType.UBYTE -> Pair(DataType.UBYTE, null)
BaseDataType.BYTE -> Pair(DataType.BYTE, left)
BaseDataType.UWORD -> Pair(DataType.UWORD, left)
BaseDataType.WORD -> Pair(DataType.WORD, left)
BaseDataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> Pair(leftDt, null) // non-numeric datatype
}
}
BaseDataType.BYTE -> {
when (rightDt.base) {
BaseDataType.UBYTE -> Pair(DataType.BYTE, right)
BaseDataType.BYTE -> Pair(DataType.BYTE, null)
BaseDataType.UWORD -> Pair(DataType.WORD, left)
BaseDataType.WORD -> Pair(DataType.WORD, left)
BaseDataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> Pair(leftDt, null) // non-numeric datatype
}
}
BaseDataType.UWORD -> {
when (rightDt.base) {
BaseDataType.UBYTE -> Pair(DataType.UWORD, right)
BaseDataType.BYTE -> Pair(DataType.WORD, right)
BaseDataType.UWORD -> Pair(DataType.UWORD, null)
BaseDataType.WORD -> Pair(DataType.WORD, left)
BaseDataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> Pair(leftDt, null) // non-numeric datatype
}
}
BaseDataType.WORD -> {
when (rightDt.base) {
BaseDataType.UBYTE -> Pair(DataType.WORD, right)
BaseDataType.BYTE -> Pair(DataType.WORD, right)
BaseDataType.UWORD -> Pair(DataType.WORD, right)
BaseDataType.WORD -> Pair(DataType.WORD, null)
BaseDataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> Pair(leftDt, null) // non-numeric datatype
}
}
BaseDataType.FLOAT -> {
Pair(DataType.FLOAT, right)
}
else -> Pair(leftDt, null) // non-numeric datatype
}
}
}
}
class ArrayIndexedExpression(var arrayvar: IdentifierReference,
val indexer: ArrayIndex,
override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
arrayvar.linkParents(this)
indexer.linkParents(this)
}
override val isSimple = indexer.indexExpr is NumericLiteral || indexer.indexExpr is IdentifierReference
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===arrayvar -> arrayvar = replacement as IdentifierReference
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun constValue(program: Program): NumericLiteral? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>) = arrayvar.referencesIdentifier(nameInSource) || indexer.referencesIdentifier(nameInSource)
override fun inferType(program: Program): InferredTypes.InferredType {
val target = arrayvar.targetStatement(program.builtinFunctions)
if (target is VarDecl) {
return when {
target.datatype.isString || target.datatype.isUnsignedWord -> InferredTypes.knownFor(BaseDataType.UBYTE)
target.datatype.isArray -> InferredTypes.knownFor(target.datatype.elementType())
target.datatype.isPointer -> {
if(target.datatype.subType!=null)
InferredTypes.knownFor(DataType.structInstance(target.datatype.subType))
else if(target.datatype.subTypeFromAntlr!=null)
InferredTypes.unknown()
else
InferredTypes.knownFor(target.datatype.sub!!)
}
else -> InferredTypes.knownFor(target.datatype)
}
} else {
val dt = arrayvar.inferType(program).getOrUndef()
if(dt.isPointer) {
if(dt.sub!=null)
return InferredTypes.knownFor(dt.sub!!)
}
}
return InferredTypes.unknown()
}
override fun toString(): String {
return "ArrayIndexed(ident=$arrayvar, idx=$indexer; pos=$position)"
}
override fun copy() = ArrayIndexedExpression(arrayvar.copy(), indexer.copy(), position)
}
class TypecastExpression(var expression: Expression, var type: DataType, val implicit: Boolean, override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
expression.linkParents(this)
}
override val isSimple = expression.isSimple
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===expression)
expression = replacement
replacement.parent = this
}
override fun copy() = TypecastExpression(expression.copy(), type, implicit, position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>) = expression.referencesIdentifier(nameInSource)
override fun inferType(program: Program) = InferredTypes.knownFor(type)
override fun constValue(program: Program): NumericLiteral? {
if(!type.isBasic)
return null
val cv = expression.constValue(program) ?: return null
cv.linkParents(parent)
val cast = cv.cast(type.base, implicit)
return if(cast.isValid) {
val newval = cast.valueOrZero()
newval.linkParents(parent)
return newval
}
else
null
}
override fun toString(): String {
return "Typecast($expression as $type)"
}
}
data class AddressOf(var identifier: IdentifierReference?, var arrayIndex: ArrayIndex?, var dereference: PtrDereference?, val msb: Boolean, override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
identifier?.linkParents(this)
arrayIndex?.linkParents(this)
dereference?.linkParents(this)
}
override val isSimple = true
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===identifier -> {
if(replacement is IdentifierReference) {
identifier = replacement
arrayIndex = null
dereference = null
} else {
dereference = replacement as PtrDereference
identifier = null
arrayIndex = null
}
}
node===arrayIndex -> {
require(replacement is ArrayIndex)
arrayIndex = replacement
}
else -> {
throw FatalAstException("invalid replace, no child node $node")
}
}
replacement.parent = this
}
override fun copy() = AddressOf(identifier?.copy(), arrayIndex?.copy(), dereference?.copy(), msb, position)
override fun constValue(program: Program): NumericLiteral? {
if(msb)
return null
val target = this.identifier?.targetStatement()
val targetVar = target as? VarDecl
if(targetVar!=null) {
if (targetVar.type == VarDeclType.MEMORY || targetVar.type == VarDeclType.CONST) {
var address = targetVar.value?.constValue(program)?.number
if (address != null) {
if (arrayIndex != null) {
val index = arrayIndex?.constIndex()
if (index != null) {
address += when {
target.datatype.isUnsignedWord -> index
target.datatype.isArray -> program.memsizer.memorySize(targetVar.datatype, index)
else -> throw FatalAstException("need array or uword ptr")
}
} else
return null
}
return NumericLiteral(BaseDataType.UWORD, address, position)
}
}
}
val targetAsmAddress = (target as? Subroutine)?.asmAddress
if (targetAsmAddress != null) {
val constAddress = targetAsmAddress.address.constValue(program)
if (constAddress == null)
return null
return NumericLiteral(BaseDataType.UWORD, constAddress.number, position)
}
return null
}
override fun referencesIdentifier(nameInSource: List<String>) = identifier?.nameInSource==nameInSource || arrayIndex?.referencesIdentifier(nameInSource)==true || dereference?.referencesIdentifier(nameInSource)==true
override fun inferType(program: Program): InferredTypes.InferredType {
if(identifier!=null) {
val type = identifier!!.inferType(program).getOrUndef()
val addrofDt = type.typeForAddressOf(msb)
if(addrofDt.isUndefined) return InferredTypes.unknown()
else return InferredTypes.knownFor(addrofDt)
} else if(dereference!=null) {
TODO("address-of struct ptr deref field -> ptr type itself?")
} else
throw FatalAstException("invalid addressof")
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
}
class DirectMemoryRead(var addressExpression: Expression, override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
this.addressExpression.linkParents(this)
}
override val isSimple = addressExpression is NumericLiteral || addressExpression is IdentifierReference
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===addressExpression)
addressExpression = replacement
replacement.parent = this
}
override fun copy() = DirectMemoryRead(addressExpression.copy(), position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>) = addressExpression.referencesIdentifier(nameInSource)
override fun inferType(program: Program) = InferredTypes.knownFor(BaseDataType.UBYTE)
override fun constValue(program: Program): NumericLiteral? = null
override fun toString(): String {
return "DirectMemoryRead($addressExpression)"
}
}
class NumericLiteral(val type: BaseDataType, // only numerical types allowed + bool (there is no separate BooleanLiteral node)
numbervalue: Double, // can be byte, word or float depending on the type
override val position: Position) : Expression() {
override lateinit var parent: Node
val number: Double by lazy {
if(type==BaseDataType.FLOAT)
numbervalue
else {
val trunc = truncate(numbervalue)
if(trunc != numbervalue)
throw ExpressionError("refused truncating of float to avoid loss of precision", position)
trunc
}
}
init {
when(type) {
BaseDataType.UBYTE -> require(numbervalue in 0.0..255.0)
BaseDataType.BYTE -> require(numbervalue in -128.0..127.0)
BaseDataType.UWORD -> require(numbervalue in 0.0..65535.0)
BaseDataType.WORD -> require(numbervalue in -32768.0..32767.0)
BaseDataType.LONG -> require(numbervalue in -2147483647.0..2147483647.0)
BaseDataType.BOOL -> require(numbervalue==0.0 || numbervalue==1.0)
else -> require(type.isNumericOrBool) { "numeric literal type should be numeric or bool: $type" }
}
}
override val isSimple = true
override fun copy() = NumericLiteral(type, number, position)
companion object {
fun fromBoolean(bool: Boolean, position: Position) =
NumericLiteral(BaseDataType.BOOL, if(bool) 1.0 else 0.0, position)
fun optimalNumeric(origType1: BaseDataType, origType2: BaseDataType?, value: Number, position: Position) : NumericLiteral =
fromOptimal(optimalNumeric(value, position), origType1, origType2, position)
fun optimalInteger(origType1: BaseDataType, origType2: BaseDataType?, value: Int, position: Position): NumericLiteral =
fromOptimal(optimalInteger(value, position), origType1, origType2, position)
fun optimalNumeric(value: Number, position: Position): NumericLiteral {
val digits = floor(value.toDouble()) - value.toDouble()
return if(value is Double && digits!=0.0) {
NumericLiteral(BaseDataType.FLOAT, value, position)
} else {
val dvalue = value.toDouble()
when (value.toInt()) {
in 0..255 -> NumericLiteral(BaseDataType.UBYTE, dvalue, position)
in -128..127 -> NumericLiteral(BaseDataType.BYTE, dvalue, position)
in 0..65535 -> NumericLiteral(BaseDataType.UWORD, dvalue, position)
in -32768..32767 -> NumericLiteral(BaseDataType.WORD, dvalue, position)
in -2147483647..2147483647 -> NumericLiteral(BaseDataType.LONG, dvalue, position)
else -> NumericLiteral(BaseDataType.FLOAT, dvalue, position)
}
}
}
fun optimalInteger(value: Int, position: Position): NumericLiteral {
val dvalue = value.toDouble()
return when (value) {
in 0..255 -> NumericLiteral(BaseDataType.UBYTE, dvalue, position)
in -128..127 -> NumericLiteral(BaseDataType.BYTE, dvalue, position)
in 0..65535 -> NumericLiteral(BaseDataType.UWORD, dvalue, position)
in -32768..32767 -> NumericLiteral(BaseDataType.WORD, dvalue, position)
in -2147483647..2147483647 -> NumericLiteral(BaseDataType.LONG, dvalue, position)
else -> throw FatalAstException("integer overflow: $dvalue")
}
}
fun optimalInteger(value: UInt, position: Position): NumericLiteral {
return when (value) {
in 0u..255u -> NumericLiteral(BaseDataType.UBYTE, value.toDouble(), position)
in 0u..65535u -> NumericLiteral(BaseDataType.UWORD, value.toDouble(), position)
in 0u..2147483647u -> NumericLiteral(BaseDataType.LONG, value.toDouble(), position)
else -> throw FatalAstException("unsigned integer overflow: $value")
}
}
private fun fromOptimal(optimal: NumericLiteral, origType1: BaseDataType, origType2: BaseDataType?, position: Position): NumericLiteral {
var largestOrig = if(origType2==null) origType1 else if(origType1.largerSizeThan(origType2)) origType1 else origType2
return if(largestOrig.largerSizeThan(optimal.type)) {
if(optimal.number<0 && !largestOrig.isSigned) {
when(largestOrig) {
BaseDataType.BOOL -> {}
BaseDataType.UBYTE -> largestOrig = BaseDataType.BYTE
BaseDataType.UWORD -> largestOrig = BaseDataType.WORD
else -> throw FatalAstException("invalid dt")
}
}
NumericLiteral(largestOrig, optimal.number, position)
}
else
optimal
}
}
val asBooleanValue: Boolean = number != 0.0
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
override fun referencesIdentifier(nameInSource: List<String>) = false
override fun constValue(program: Program): NumericLiteral {
return copy().also {
if(::parent.isInitialized)
it.parent = parent
}
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun toString(): String = "NumericLiteral(${type.name}:$number)"
override fun inferType(program: Program): InferredTypes.InferredType = when(type) {
BaseDataType.UBYTE -> InferredTypes.knownFor(BaseDataType.UBYTE)
BaseDataType.BYTE -> InferredTypes.knownFor(BaseDataType.BYTE)
BaseDataType.UWORD -> InferredTypes.knownFor(BaseDataType.UWORD)
BaseDataType.WORD -> InferredTypes.knownFor(BaseDataType.WORD)
BaseDataType.LONG -> InferredTypes.knownFor(BaseDataType.LONG)
BaseDataType.FLOAT -> InferredTypes.knownFor(BaseDataType.FLOAT)
BaseDataType.BOOL -> InferredTypes.knownFor(BaseDataType.BOOL)
BaseDataType.STR -> InferredTypes.knownFor(BaseDataType.STR)
else -> throw IllegalArgumentException("invalid type for numeric literal: $type")
}
override fun hashCode(): Int = Objects.hash(type, number)
override fun equals(other: Any?): Boolean {
return if(other==null || other !is NumericLiteral)
false
else if(type!=BaseDataType.BOOL && other.type!=BaseDataType.BOOL)
number==other.number
else
type==other.type && number==other.number
}
operator fun compareTo(other: NumericLiteral): Int = number.compareTo(other.number)
class ValueAfterCast(val isValid: Boolean, val whyFailed: String?, private val value: NumericLiteral?) {
fun valueOrZero() = if(isValid) value!! else NumericLiteral(BaseDataType.UBYTE, 0.0, Position.DUMMY)
fun linkParent(parent: Node) {
value?.linkParents(parent)
}
}
fun cast(targettype: BaseDataType, implicit: Boolean): ValueAfterCast {
val result = internalCast(targettype, implicit)
result.linkParent(this.parent)
return result
}
private fun internalCast(targettype: BaseDataType, implicit: Boolean): ValueAfterCast {
// NOTE: this MAY convert a value into another when switching from singed to unsigned!!!
if(type==targettype)
return ValueAfterCast(true, null, this)
if (implicit && targettype.isInteger && type== BaseDataType.BOOL)
return ValueAfterCast(false, "no implicit cast from boolean to integer allowed", this)
if(targettype == BaseDataType.BOOL) {
return if(implicit)
ValueAfterCast(false, "no implicit cast to boolean allowed", this)
else if(type.isNumeric)
ValueAfterCast(true, null, fromBoolean(number!=0.0, position))
else
ValueAfterCast(false, "no cast available from $type to BOOL", null)
}
when(type) {
BaseDataType.UBYTE -> {
if(targettype==BaseDataType.BYTE && (number in 0.0..127.0 || !implicit))
return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toByte().toDouble(), position))
if(targettype==BaseDataType.WORD || targettype==BaseDataType.UWORD)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if(targettype==BaseDataType.FLOAT)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if(targettype==BaseDataType.LONG)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if(targettype.isPointer)
return ValueAfterCast(true, null, NumericLiteral(BaseDataType.UWORD, number, position))
}
BaseDataType.BYTE -> {
if(targettype==BaseDataType.UBYTE) {
if(number in -128.0..0.0 && !implicit)
return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toUByte().toDouble(), position))
else if(number in 0.0..255.0)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
}
if(targettype==BaseDataType.UWORD) {
if(number in -32768.0..0.0 && !implicit)
return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toUShort().toDouble(), position))
else if(number in 0.0..65535.0)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
}
if(targettype==BaseDataType.WORD)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if(targettype==BaseDataType.FLOAT)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if(targettype==BaseDataType.LONG)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
}
BaseDataType.UWORD -> {
if(targettype==BaseDataType.BYTE && number <= 127)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if(targettype==BaseDataType.UBYTE && number <= 255)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if(targettype==BaseDataType.WORD && (number <= 32767 || !implicit))
return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toShort().toDouble(), position))
if(targettype==BaseDataType.FLOAT)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if(targettype==BaseDataType.LONG)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
}
BaseDataType.WORD -> {
if(targettype==BaseDataType.BYTE && number >= -128 && number <=127)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if(targettype==BaseDataType.UBYTE) {
if(number in -128.0..0.0 && !implicit)
return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toUByte().toDouble(), position))
else if(number in 0.0..255.0)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
}
if(targettype==BaseDataType.UWORD) {
if(number in -32768.0 .. 0.0 && !implicit)
return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toUShort().toDouble(), position))
else if(number in 0.0..65535.0)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
}
if(targettype==BaseDataType.FLOAT)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if(targettype==BaseDataType.LONG)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
}
BaseDataType.FLOAT -> {
try {
if (targettype == BaseDataType.BYTE && number >= -128 && number <= 127)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if (targettype == BaseDataType.UBYTE && number >= 0 && number <= 255)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if (targettype == BaseDataType.WORD && number >= -32768 && number <= 32767)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if (targettype == BaseDataType.UWORD && number >= 0 && number <= 65535)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if(targettype==BaseDataType.LONG && number >=0 && number <= 2147483647)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
} catch (x: ExpressionError) {
return ValueAfterCast(false, x.message,null)
}
}
BaseDataType.BOOL -> {
if(implicit)
return ValueAfterCast(false, "no implicit cast from boolean to integer allowed", null)
else if(targettype.isInteger)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
}
BaseDataType.LONG -> {
try {
if (targettype == BaseDataType.BYTE && number >= -128 && number <= 127)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if (targettype == BaseDataType.UBYTE && number >= 0 && number <= 255)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if (targettype == BaseDataType.WORD && number >= -32768 && number <= 32767)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if (targettype == BaseDataType.UWORD && number >= 0 && number <= 65535)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if(targettype==BaseDataType.FLOAT)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
} catch (x: ExpressionError) {
return ValueAfterCast(false, x.message, null)
}
}
else -> {
throw FatalAstException("type cast of weird type $type")
}
}
return ValueAfterCast(false, "no cast available from $type to $targettype number=$number", null)
}
fun convertTypeKeepValue(targetDt: BaseDataType): ValueAfterCast {
if(type==targetDt)
return ValueAfterCast(true, null, this)
when(type) {
BaseDataType.UBYTE -> {
when(targetDt) {
BaseDataType.BYTE -> if(number<=127.0) return cast(targetDt, false)
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT -> return cast(targetDt, false)
BaseDataType.POINTER -> return cast(targetDt, false)
else -> {}
}
}
BaseDataType.BYTE -> {
when(targetDt) {
BaseDataType.UBYTE, BaseDataType.UWORD -> if(number>=0.0) return cast(targetDt, false)
BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT -> return cast(targetDt, false)
else -> {}
}
}
BaseDataType.UWORD -> {
when(targetDt) {
BaseDataType.UBYTE -> if(number<=255.0) return cast(targetDt, false)
BaseDataType.BYTE -> if(number<=127.0) return cast(targetDt, false)
BaseDataType.WORD -> if(number<=32767.0) return cast(targetDt, false)
BaseDataType.LONG, BaseDataType.FLOAT -> return cast(targetDt, false)
BaseDataType.POINTER -> return cast(targetDt, false)
else -> {}
}
}
BaseDataType.WORD -> {
when(targetDt) {
BaseDataType.UBYTE -> if(number in 0.0..255.0) return cast(targetDt, false)
BaseDataType.BYTE -> if(number in -128.0..127.0) return cast(targetDt, false)
BaseDataType.UWORD -> if(number in 0.0..32767.0) return cast(targetDt, false)
BaseDataType.LONG, BaseDataType.FLOAT -> return cast(targetDt, false)
else -> {}
}
}
BaseDataType.LONG, BaseDataType.FLOAT -> return cast(targetDt, false)
else -> {}
}
return ValueAfterCast(false, "no type conversion possible from $type to $targetDt", null)
}
}
class CharLiteral private constructor(val value: Char,
var encoding: Encoding,
override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
}
companion object {
fun create(character: Char, encoding: Encoding, position: Position): CharLiteral {
return if(encoding==Encoding.KATAKANA) {
val processed = JapaneseCharacterConverter.zenkakuKatakanaToHankakuKatakana(character.toString())
if(processed.length==1)
CharLiteral(processed[0], encoding, position)
else
throw CharConversionException("character literal encodes into multiple bytes at $position")
} else
CharLiteral(character, encoding, position)
}
fun fromEscaped(raw: String, encoding: Encoding, position: Position): CharLiteral {
val unescaped = raw.unescape()
return create(unescaped[0], encoding, position)
}
}
override val isSimple = true
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
override fun copy() =
CharLiteral(value, encoding, position)
override fun referencesIdentifier(nameInSource: List<String>) = false
override fun constValue(program: Program): NumericLiteral {
val bytevalue = program.encoding.encodeString(value.toString(), encoding).single()
return NumericLiteral(BaseDataType.UBYTE, bytevalue.toDouble(), position)
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String = "'${value.escape()}'"
override fun inferType(program: Program) = InferredTypes.knownFor(BaseDataType.UBYTE)
operator fun compareTo(other: CharLiteral): Int = value.compareTo(other.value)
override fun hashCode(): Int = Objects.hash(value, encoding)
override fun equals(other: Any?): Boolean {
if (other == null || other !is CharLiteral)
return false
return value == other.value && encoding == other.encoding
}
}
class StringLiteral private constructor(val value: String,
var encoding: Encoding,
override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
}
companion object {
fun create(str: String, encoding: Encoding, position: Position): StringLiteral {
if (encoding == Encoding.KATAKANA) {
val processed = JapaneseCharacterConverter.zenkakuKatakanaToHankakuKatakana(str)
return StringLiteral(processed, encoding, position)
} else
return StringLiteral(str, encoding, position)
}
fun fromEscaped(raw: String, encoding: Encoding, position: Position): StringLiteral = create(raw.unescape(), encoding, position)
}
override val isSimple = true
override fun copy() = StringLiteral(value, encoding, position)
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
override fun referencesIdentifier(nameInSource: List<String>) = false
override fun constValue(program: Program): NumericLiteral? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun toString(): String = "'${value.escape()}'"
override fun inferType(program: Program) = InferredTypes.knownFor(BaseDataType.STR)
operator fun compareTo(other: StringLiteral): Int = value.compareTo(other.value)
override fun hashCode(): Int = Objects.hash(value, encoding)
override fun equals(other: Any?): Boolean {
if(other==null || other !is StringLiteral)
return false
return value==other.value && encoding == other.encoding
}
}
class ArrayLiteral(val type: InferredTypes.InferredType, // inferred because not all array literals hava a known type yet
val value: Array<Expression>,
override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
value.forEach {it.linkParents(this)}
}
override fun copy(): ArrayLiteral = ArrayLiteral(type, value.map { it.copy() }.toTypedArray(), position)
override val isSimple = true
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
val idx = value.indexOfFirst { it===node }
value[idx] = replacement
replacement.parent = this
}
override fun referencesIdentifier(nameInSource: List<String>) = value.any { it.referencesIdentifier(nameInSource) }
override fun constValue(program: Program): NumericLiteral? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun toString(): String = "$value"
override fun inferType(program: Program): InferredTypes.InferredType = if(type.isKnown) type else guessDatatype(program)
operator fun compareTo(other: ArrayLiteral): Int = throw ExpressionError("cannot order compare arrays", position)
override fun hashCode(): Int = Objects.hash(value, type)
override fun equals(other: Any?): Boolean {
if(other==null || other !is ArrayLiteral)
return false
return type==other.type && value.contentEquals(other.value)
}
fun guessDatatype(program: Program): InferredTypes.InferredType {
// Educated guess of the desired array literal's datatype.
// If it's inside a for loop, assume the data type of the loop variable is what we want.
val forloop = parent as? ForLoop
if(forloop != null) {
val loopvarDt = forloop.loopVarDt(program)
if(loopvarDt.isKnown) {
return if(!loopvarDt.isNumericOrBool)
InferredTypes.unknown()
else
InferredTypes.knownFor(loopvarDt.getOrUndef().elementToArray())
}
}
// otherwise, select the "biggest" datatype based on the elements in the array.
require(value.isNotEmpty()) { "can't determine type of empty array" }
val datatypesInArray = value.map { it.inferType(program) }
if(datatypesInArray.any{ it.isUnknown })
return InferredTypes.unknown()
val dts = datatypesInArray.map { it.getOrUndef() }
if(dts.all { it.isPointer }) {
val unique = dts.toSet()
if(unique.size==1) {
val dt = unique.single()
return if(dt.subType!=null)
InferredTypes.knownFor(DataType.arrayOfPointersTo(dt.subType!!))
else
InferredTypes.knownFor(DataType.arrayOfPointersTo(dt.sub!!))
}
}
return when {
dts.any { it.isFloat } -> InferredTypes.knownFor(DataType.arrayFor(BaseDataType.FLOAT))
dts.any { it.isString } -> InferredTypes.knownFor(DataType.arrayFor(BaseDataType.UWORD))
dts.any { it.isSignedWord } -> InferredTypes.knownFor(DataType.arrayFor(BaseDataType.WORD))
dts.any { it.isUnsignedWord } -> InferredTypes.knownFor(DataType.arrayFor(BaseDataType.UWORD))
dts.any { it.isSignedByte } -> InferredTypes.knownFor(DataType.arrayFor(BaseDataType.BYTE))
dts.any { it.isBool } -> {
if(dts.all { it.isBool})
InferredTypes.knownFor(DataType.arrayFor(BaseDataType.BOOL))
else
InferredTypes.unknown()
}
dts.any { it.isUnsignedByte } -> InferredTypes.knownFor(DataType.arrayFor(BaseDataType.UBYTE))
dts.any { it.isArray } -> InferredTypes.knownFor(DataType.arrayFor(BaseDataType.UWORD))
else -> InferredTypes.unknown()
}
}
fun cast(targettype: DataType): ArrayLiteral? {
if(type istype targettype)
return this
if(targettype.isArray) {
val elementType = targettype.elementType()
// if all values are numeric literals, just do the cast.
// if not:
// if all values are numeric literals OR addressof OR identifiers, and the target type is WORD or UWORD,
// do the cast for the numeric literals and leave the rest.
// otherwise: return null (cast cannot be done)
if(value.all { it is NumericLiteral }) {
val castArray = if(elementType.isBool) {
value.map {
if((it as NumericLiteral).type==BaseDataType.BOOL)
it
else
return null // abort
}
} else {
if(!elementType.isNumericOrBool)
return null // only a numeric or boolean array can be casted to another value
value.map {
val cast = (it as NumericLiteral).cast(elementType.base, true)
if(cast.isValid)
cast.valueOrZero()
else
return null // abort
}
}
return ArrayLiteral(InferredTypes.InferredType.known(targettype), castArray.toTypedArray(), position = position)
}
else if(elementType.isWord && value.all { it is NumericLiteral || it is AddressOf || it is IdentifierReference}) {
val castArray = value.map {
when(it) {
is AddressOf -> it
is IdentifierReference -> it
is NumericLiteral -> {
val numcast = it.cast(elementType.base, true)
if(numcast.isValid)
numcast.valueOrZero()
else
return null // abort
}
else -> return null // abort
}
}.toTypedArray()
return ArrayLiteral(InferredTypes.InferredType.known(targettype), castArray, position = position)
}
else
return null
}
return null // invalid type conversion from $this to $targettype
}
}
class RangeExpression(var from: Expression,
var to: Expression,
var step: Expression,
override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
from.linkParents(this)
to.linkParents(this)
step.linkParents(this)
}
override val isSimple = true
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
when {
from===node -> from=replacement
to===node -> to=replacement
step===node -> step=replacement
else -> throw FatalAstException("invalid replacement")
}
replacement.parent = this
}
override fun copy() = RangeExpression(from.copy(), to.copy(), step.copy(), position)
override fun constValue(program: Program): NumericLiteral? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>): Boolean = from.referencesIdentifier(nameInSource) || to.referencesIdentifier(nameInSource) || step.referencesIdentifier(nameInSource)
override fun inferType(program: Program): InferredTypes.InferredType {
val fromDt=from.inferType(program)
val toDt=to.inferType(program)
return when {
!fromDt.isKnown || !toDt.isKnown -> InferredTypes.unknown()
fromDt istype DataType.UBYTE && toDt istype DataType.UBYTE -> InferredTypes.knownFor(DataType.arrayFor(BaseDataType.UBYTE))
fromDt istype DataType.UWORD && toDt istype DataType.UWORD -> InferredTypes.knownFor(DataType.arrayFor(BaseDataType.UWORD))
fromDt istype DataType.STR && toDt istype DataType.STR -> InferredTypes.knownFor(BaseDataType.STR)
fromDt istype DataType.WORD || toDt istype DataType.WORD -> InferredTypes.knownFor(DataType.arrayFor(BaseDataType.WORD))
fromDt istype DataType.BYTE || toDt istype DataType.BYTE -> InferredTypes.knownFor(DataType.arrayFor(BaseDataType.BYTE))
else -> {
val fdt = fromDt.getOrUndef()
val tdt = toDt.getOrUndef()
if(fdt.largerSizeThan(tdt))
InferredTypes.knownFor(fdt.elementToArray())
else
InferredTypes.knownFor(tdt.elementToArray())
}
}
}
override fun toString(): String {
return "RangeExpr(from $from, to $to, step $step, pos=$position)"
}
fun toConstantIntegerRange(): IntProgression? {
fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
return when {
fromVal == toVal -> fromVal .. toVal
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
else -> fromVal..toVal step stepVal
}
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
}
}
}
val fromLv = from as? NumericLiteral
val toLv = to as? NumericLiteral
val stepLv = step as? NumericLiteral
if(fromLv==null || toLv==null || stepLv==null)
return null
val fromVal = fromLv.number.toInt()
val toVal = toLv.number.toInt()
val stepVal = stepLv.number.toInt()
return makeRange(fromVal, toVal, stepVal)
}
fun size(): Int? {
val fromLv = (from as? NumericLiteral)
val toLv = (to as? NumericLiteral)
if(fromLv==null || toLv==null)
return null
return toConstantIntegerRange()?.count()
}
}
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression() {
override lateinit var parent: Node
override val isSimple = true
fun targetStatement(builtins: IBuiltinFunctions? = null): Statement? =
if(builtins!=null && nameInSource.singleOrNull() in builtins.names)
BuiltinFunctionPlaceholder(nameInSource[0], position, parent)
else
definingScope.lookup(nameInSource)
fun targetVarDecl(): VarDecl? = targetStatement() as? VarDecl
fun targetSubroutine(): Subroutine? = targetStatement() as? Subroutine
fun targetStructDecl(): StructDecl? = targetStatement() as? StructDecl
fun targetStructFieldRef(): StructFieldRef? = targetStatement() as? StructFieldRef
fun targetNameAndType(program: Program): Pair<String, DataType> {
val target = targetStatement(program.builtinFunctions) as? INamedStatement ?: throw FatalAstException("can't find target for $nameInSource")
val targetname: String = if(target.name in program.builtinFunctions.names)
"<builtin>.${target.name}"
else
target.scopedName.joinToString(".")
val type = inferType(program).getOrUndef()
return Pair(targetname, type)
}
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
override fun copy() = IdentifierReference(nameInSource, position)
override fun constValue(program: Program): NumericLiteral? {
val node = definingScope.lookup(nameInSource)
if(node==null) {
// maybe not a statement but perhaps a subroutine parameter?
(definingScope as? Subroutine)?.let { sub ->
if(sub.parameters.any { it.name==nameInSource.last() })
return null
}
return null
}
if(node is StructFieldRef)
return null
val vardecl = node as? VarDecl
if(vardecl==null) {
return null
} else if(vardecl.type!= VarDeclType.CONST) {
return null
}
// the value of a variable can (temporarily) be a different type as the vardecl itself.
// don't return the value if the types don't match yet!
val value = vardecl.value?.constValue(program)
if(value==null || value.type==vardecl.datatype.base)
return value
val optimal = NumericLiteral.optimalNumeric(value.number, value.position)
if(optimal.type==vardecl.datatype.base)
return optimal
return null
}
override fun toString(): String {
return "IdentifierRef($nameInSource)"
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>): Boolean = this.nameInSource==nameInSource
override fun inferType(program: Program): InferredTypes.InferredType {
return when (val targetStmt = targetStatement(program.builtinFunctions)) {
is VarDecl -> {
if(targetStmt.datatype.isUndefined)
InferredTypes.unknown()
else
InferredTypes.knownFor(targetStmt.datatype)
}
null -> {
val param = definingSubroutine?.parameters?.find { it.name==nameInSource.singleOrNull() }
if(param!=null)
return InferredTypes.knownFor(param.type)
val fieldType = traverseDerefChainForDt(null)
if(fieldType.isUndefined)
InferredTypes.unknown()
else
InferredTypes.knownFor(fieldType)
}
is StructDecl -> {
InferredTypes.unknown() // the type of a structdecl itself is actually not defined
}
is StructFieldRef -> {
InferredTypes.knownFor(targetStmt.type)
}
else -> InferredTypes.unknown()
}
}
fun traverseDerefChainForDt(startStruct: StructDecl?): DataType {
var struct: StructDecl
var fieldDt: DataType? = null
if(startStruct!=null) {
struct = startStruct
}
else {
val vardecl = definingScope.lookup(nameInSource.take(1)) as? VarDecl
if (vardecl?.datatype?.isPointer != true)
return DataType.UNDEFINED
require(vardecl.datatype.subType!=null) { "pointer type should point to a struct ${vardecl.position}" }
struct = vardecl.datatype.subType as StructDecl
fieldDt = vardecl.datatype
}
for((idx, field) in nameInSource.drop(1).withIndex()) {
fieldDt = struct.getFieldType(field)
if(fieldDt==null)
return DataType.UNDEFINED
if(idx==nameInSource.size-2) {
// was last path element
return fieldDt
}
struct = fieldDt.subType as? StructDecl ?: return DataType.UNDEFINED
}
return fieldDt ?: DataType.UNDEFINED
}
fun wasStringLiteral(): Boolean {
val decl = targetVarDecl()
if(decl == null || decl.origin!=VarDeclOrigin.STRINGLITERAL)
return false
val scope=decl.definingModule
return scope.name==INTERNED_STRINGS_MODULENAME
}
}
class FunctionCallExpression(override var target: IdentifierReference,
override val args: MutableList<Expression>,
override val position: Position) : Expression(), IFunctionCall {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
target.linkParents(this)
args.forEach { it.linkParents(this) }
}
override fun copy() = FunctionCallExpression(target.copy(), args.map { it.copy() }.toMutableList(), position)
override val isSimple = when (target.nameInSource.singleOrNull()) {
in arrayOf("msb", "lsb", "mkword", "set_carry", "set_irqd", "clear_carry", "clear_irqd") -> this.args.all { it.isSimple }
else -> false
}
override fun replaceChildNode(node: Node, replacement: Node) {
if(node===target)
target=replacement as IdentifierReference
else {
val idx = args.indexOfFirst { it===node }
args[idx] = replacement as Expression
}
replacement.parent = this
}
override fun constValue(program: Program) = constValue(program, true)
private fun constValue(program: Program, withDatatypeCheck: Boolean): NumericLiteral? {
// if the function is a built-in function and the args are consts, should try to const-evaluate!
// lenghts of arrays and strings are constants that are determined at compile time!
if(target.nameInSource.size>1)
return null
val resultValue: NumericLiteral? = program.builtinFunctions.constValue(target.nameInSource[0], args, position)
if(withDatatypeCheck) {
val resultDt = this.inferType(program)
if(resultValue==null || resultDt istype DataType.forDt(resultValue.type))
return resultValue
throw FatalAstException("evaluated const expression result value doesn't match expected datatype $resultDt, pos=$position")
} else {
return resultValue
}
}
override fun toString(): String {
return "FunctionCall(target=$target, pos=$position)"
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>): Boolean = target.referencesIdentifier(nameInSource) || args.any{it.referencesIdentifier(nameInSource)}
override fun inferType(program: Program): InferredTypes.InferredType {
val constVal = constValue(program ,false)
if(constVal!=null)
return InferredTypes.knownFor(constVal.type)
val stmt = target.targetStatement(program.builtinFunctions) ?: return InferredTypes.unknown()
when (stmt) {
is BuiltinFunctionPlaceholder -> {
return program.builtinFunctions.returnType(target.nameInSource[0])
}
is Subroutine -> {
if(stmt.returntypes.isEmpty())
return InferredTypes.void() // no return value
if(stmt.returntypes.size==1)
return InferredTypes.knownFor(stmt.returntypes[0])
return InferredTypes.unknown() // has multiple return types... so not a single resulting datatype possible
}
is StructDecl -> {
// calling a struct is syntax for allocating a static instance, and returns a pointer to that (not the instance itself)
return InferredTypes.knownFor(DataType.pointer(stmt))
}
is StructFieldRef -> throw FatalAstException("cannot call a struct field $stmt")
else -> return InferredTypes.unknown()
}
}
}
class ContainmentCheck(var element: Expression,
var iterable: Expression,
override val position: Position): Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
element.parent = this
iterable.linkParents(this)
}
override val isSimple: Boolean = false
override fun copy() = ContainmentCheck(element.copy(), iterable.copy(), position)
override fun constValue(program: Program): NumericLiteral? {
val elementConst = element.constValue(program)
if(elementConst!=null) {
when(iterable){
is ArrayLiteral -> {
val exists = (iterable as ArrayLiteral).value.any { it.constValue(program)==elementConst }
return NumericLiteral.fromBoolean(exists, position)
}
is StringLiteral -> {
if(elementConst.type.isByte) {
val stringval = iterable as StringLiteral
val exists = program.encoding.encodeString(stringval.value, stringval.encoding).contains(elementConst.number.toInt().toUByte() )
return NumericLiteral.fromBoolean(exists, position)
}
}
is RangeExpression -> {
if(elementConst.type.isInteger) {
val intprogression = (iterable as RangeExpression).toConstantIntegerRange()
if (intprogression!=null) {
return NumericLiteral.fromBoolean(intprogression.contains(elementConst.number.toInt()), position)
}
}
}
else -> {}
}
}
when(iterable){
is ArrayLiteral -> {
val array= iterable as ArrayLiteral
if(array.value.isEmpty())
return NumericLiteral.fromBoolean(false, position)
}
is StringLiteral -> {
if((iterable as StringLiteral).value.isEmpty())
return NumericLiteral.fromBoolean(false, position)
}
else -> {}
}
return null
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>): Boolean = element.referencesIdentifier(nameInSource) || iterable.referencesIdentifier(nameInSource)
override fun inferType(program: Program) = InferredTypes.knownFor(BaseDataType.BOOL)
override fun replaceChildNode(node: Node, replacement: Node) {
if(replacement !is Expression)
throw FatalAstException("invalid replace")
if(node===element)
element=replacement
else if(node===iterable)
iterable=replacement
else
throw FatalAstException("invalid replace")
}
}
class IfExpression(var condition: Expression, var truevalue: Expression, var falsevalue: Expression, override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
condition.linkParents(this)
truevalue.linkParents(this)
falsevalue.linkParents(this)
}
override val isSimple: Boolean = condition.isSimple && truevalue.isSimple && falsevalue.isSimple
override fun toString() = "IfExpr(cond=$condition, true=$truevalue, false=$falsevalue, pos=$position)"
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>): Boolean = condition.referencesIdentifier(nameInSource) || truevalue.referencesIdentifier(nameInSource) || falsevalue.referencesIdentifier(nameInSource)
override fun inferType(program: Program): InferredTypes.InferredType {
val t1 = truevalue.inferType(program)
val t2 = falsevalue.inferType(program)
return if(t1==t2) t1 else InferredTypes.unknown()
}
override fun copy(): Expression = IfExpression(condition.copy(), truevalue.copy(), falsevalue.copy(), position)
override fun constValue(program: Program): NumericLiteral? {
val cond = condition.constValue(program)
if(cond!=null) {
return if (cond.asBooleanValue) truevalue.constValue(program) else falsevalue.constValue(program)
}
return null
}
override fun replaceChildNode(node: Node, replacement: Node) {
if(replacement !is Expression)
throw FatalAstException("invalid replace")
if(node===condition) condition=replacement
else if(node===truevalue) truevalue=replacement
else if(node===falsevalue) falsevalue=replacement
else throw FatalAstException("invalid replace")
}
}
class PtrIndexedDereference(val indexed: ArrayIndexedExpression, override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
indexed.linkParents(this)
}
override val isSimple = false
override fun copy() = PtrIndexedDereference(indexed.copy(), position)
override fun constValue(program: Program): NumericLiteral? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun inferType(program: Program): InferredTypes.InferredType {
val parentExpr = parent as? BinaryExpression
if(parentExpr?.operator==".") {
TODO("cannot determine type of dereferenced indexed pointer(?) as part of a larger dereference expression")
}
val vardecl = indexed.arrayvar.targetVarDecl()
if(vardecl!=null &&vardecl.datatype.isPointer) {
if(vardecl.datatype.sub!=null)
return InferredTypes.knownFor(vardecl.datatype.dereference())
TODO("cannot determine type of dereferenced indexed pointer(?) that is not a pointer to a basic type")
}
if(parent is AssignTarget || parent is Assignment) {
val dt = indexed.arrayvar.traverseDerefChainForDt(null)
return when {
dt.isUndefined -> InferredTypes.unknown()
dt.isUnsignedWord -> InferredTypes.knownFor(BaseDataType.UBYTE)
dt.isPointer -> {
return if(dt.sub!=null) InferredTypes.knownFor(dt.dereference())
else InferredTypes.unknown()
}
else -> InferredTypes.unknown()
}
}
return InferredTypes.unknown()
}
override fun replaceChildNode(node: Node, replacement: Node) =
throw FatalAstException("can't replace here")
override fun referencesIdentifier(nameInSource: List<String>) = indexed.referencesIdentifier(nameInSource)
}
class PtrDereference(val identifier: IdentifierReference, val chain: List<String>, val field: String?, override val position: Position) : Expression() {
// TODO why both identifier and chain?
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
identifier.linkParents(this)
}
override val isSimple = false
override fun copy(): PtrDereference = PtrDereference(identifier.copy(), chain.toList(), field, position)
override fun constValue(program: Program): NumericLiteral? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun inferType(program: Program): InferredTypes.InferredType {
val first = identifier.targetStatement()
if(first==null)
return InferredTypes.unknown()
if(first is StructFieldRef) {
return InferredTypes.knownFor(first.type.dereference())
}
val vardecl = identifier.targetVarDecl()
if(vardecl==null || vardecl.datatype.isUndefined || (!vardecl.datatype.isPointer && !vardecl.datatype.isStructInstance) )
return InferredTypes.unknown()
if(chain.isEmpty()) {
return if(field==null) {
require(vardecl.datatype.sub!=null) { "can only dereference a pointer to a simple datatype " }
InferredTypes.knownFor(vardecl.datatype.dereference())
} else {
// lookup struct field type
val struct = vardecl.datatype.subType as StructDecl
val fieldDt = struct.getFieldType(field)
if (fieldDt == null)
InferredTypes.unknown()
else
InferredTypes.knownFor(fieldDt.dereference())
}
} else {
// lookup type of field at the end of a dereference chain
var struct = vardecl.datatype.subType as StructDecl
chain.forEach { fieldname ->
val fieldDt = struct.getFieldType(fieldname)
if(fieldDt==null)
return InferredTypes.unknown()
if(!fieldDt.isPointer || fieldDt.subType==null)
return InferredTypes.unknown()
struct = fieldDt.subType as StructDecl
}
if(field==null) {
return InferredTypes.knownFor(DataType.structInstance(struct))
}
val fieldDt = struct.getFieldType(field)
return if(fieldDt==null)
InferredTypes.unknown()
else
InferredTypes.knownFor(fieldDt.dereference())
}
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun referencesIdentifier(nameInSource: List<String>) = identifier.referencesIdentifier(nameInSource)
}
fun invertCondition(cond: Expression, program: Program): Expression {
if(cond is BinaryExpression) {
val invertedOperator = invertedComparisonOperator(cond.operator)
if (invertedOperator != null)
return BinaryExpression(cond.left, invertedOperator, cond.right, cond.position)
}
return if(cond.inferType(program).isBool)
PrefixExpression("not", cond, cond.position)
else
BinaryExpression(cond, "==", NumericLiteral(BaseDataType.UBYTE, 0.0, cond.position), cond.position)
}