initial struct and typed pointer support

This commit is contained in:
Irmen de Jong
2025-04-25 22:55:07 +02:00
parent 7eb079050c
commit e328520588
51 changed files with 1292 additions and 777 deletions
File diff suppressed because it is too large Load Diff
+70 -28
View File
@@ -13,6 +13,8 @@ enum class BaseDataType {
STR, // pass by reference
ARRAY, // pass by reference, subtype is the element type
ARRAY_SPLITW, // pass by reference, split word layout, subtype is the element type (restricted to word types)
POINTER, // typed pointer, subtype is whatever type is pointed to
ARRAY_POINTER, // array of pointers (uwords), subtype is whatever type each element points to
UNDEFINED;
@@ -50,25 +52,33 @@ val BaseDataType.isIntegerOrBool get() = this in arrayOf(BaseDataType.UBYTE, Bas
val BaseDataType.isNumeric get() = this == BaseDataType.FLOAT || this.isInteger
val BaseDataType.isNumericOrBool get() = this == BaseDataType.BOOL || this.isNumeric
val BaseDataType.isSigned get() = this in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER
val BaseDataType.isPointer get() = this == BaseDataType.POINTER
val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW
val BaseDataType.isIterable get() = this in arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW)
val BaseDataType.isIterable get() = this in arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
val BaseDataType.isPassByRef get() = this.isIterable
val BaseDataType.isPassByValue get() = !this.isIterable
class DataType private constructor(val base: BaseDataType, val sub: BaseDataType?) {
class DataType private constructor(val base: BaseDataType, val sub: BaseDataType?, val subIdentifier: List<String>?) {
init {
if(base.isArray) {
require(sub != null)
if(base.isPointerArray) {
require(sub!=null || subIdentifier!=null)
}
else if(base.isArray) {
require(sub != null && subIdentifier==null)
if(base.isSplitWordArray)
require(sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
}
else if(base==BaseDataType.STR)
require(sub==BaseDataType.UBYTE) { "string subtype should be ubyte" }
else
require(sub == null) { "only string and array base types can have a subtype"}
else if(base!=BaseDataType.POINTER)
require(sub == null) { "only string, array and pointer base types can have a subtype"}
require(sub == null || subIdentifier == null) { "subtype and identifier can't both be set" }
}
override fun equals(other: Any?): Boolean {
@@ -81,41 +91,49 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
companion object {
val UBYTE = DataType(BaseDataType.UBYTE, null)
val BYTE = DataType(BaseDataType.BYTE, null)
val UWORD = DataType(BaseDataType.UWORD, null)
val WORD = DataType(BaseDataType.WORD, null)
val LONG = DataType(BaseDataType.LONG, null)
val FLOAT = DataType(BaseDataType.FLOAT, null)
val BOOL = DataType(BaseDataType.BOOL, null)
val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE)
val UNDEFINED = DataType(BaseDataType.UNDEFINED, null)
val UBYTE = DataType(BaseDataType.UBYTE, null, null)
val BYTE = DataType(BaseDataType.BYTE, null, null)
val UWORD = DataType(BaseDataType.UWORD, null, null)
val WORD = DataType(BaseDataType.WORD, null, null)
val LONG = DataType(BaseDataType.LONG, null, null)
val FLOAT = DataType(BaseDataType.FLOAT, null, null)
val BOOL = DataType(BaseDataType.BOOL, null, null)
val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE, null)
val UNDEFINED = DataType(BaseDataType.UNDEFINED, null, null)
private val simpletypes = mapOf(
BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null),
BaseDataType.BYTE to DataType(BaseDataType.BYTE, null),
BaseDataType.UWORD to DataType(BaseDataType.UWORD, null),
BaseDataType.WORD to DataType(BaseDataType.WORD, null),
BaseDataType.LONG to DataType(BaseDataType.LONG, null),
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null),
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null),
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE),
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null)
BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null, null),
BaseDataType.BYTE to DataType(BaseDataType.BYTE, null, null),
BaseDataType.UWORD to DataType(BaseDataType.UWORD, null, null),
BaseDataType.WORD to DataType(BaseDataType.WORD, null, null),
BaseDataType.LONG to DataType(BaseDataType.LONG, null, null),
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null, null),
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null, null),
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE, null),
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null, null)
)
fun forDt(dt: BaseDataType) = simpletypes.getValue(dt)
fun arrayFor(elementDt: BaseDataType, splitwordarray: Boolean=true): DataType {
require(!elementDt.isPointer) { "use other array constructor for arrays of pointers" }
val actualElementDt = if(elementDt==BaseDataType.STR) BaseDataType.UWORD else elementDt // array of strings is actually just an array of UWORD pointers
return if(splitwordarray && actualElementDt.isWord)
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt)
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt, null)
else {
if(actualElementDt.isNumericOrBool && actualElementDt != BaseDataType.LONG)
DataType(BaseDataType.ARRAY, actualElementDt)
DataType(BaseDataType.ARRAY, actualElementDt, null)
else
throw NoSuchElementException("invalid element dt $elementDt")
}
}
fun arrayOfPointersTo(sub: BaseDataType?, subIdentifier: List<String>?): DataType =
DataType(BaseDataType.ARRAY_POINTER, sub, subIdentifier)
fun pointer(base: BaseDataType): DataType = DataType(BaseDataType.POINTER, base, null)
fun pointer(scopedIdentifier: List<String>): DataType = DataType(BaseDataType.POINTER, null, scopedIdentifier)
}
fun elementToArray(splitwords: Boolean = true): DataType {
@@ -124,7 +142,9 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
}
fun elementType(): DataType =
if(base.isArray || base==BaseDataType.STR)
if(isPointerArray)
DataType(BaseDataType.POINTER, sub, subIdentifier)
else if(base.isArray || base==BaseDataType.STR)
forDt(sub!!)
else
throw IllegalArgumentException("not an array")
@@ -148,6 +168,12 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
else -> throw IllegalArgumentException("invalid sub type")
}
}
BaseDataType.POINTER -> {
if(sub!=null) "^${sub.name.lowercase()}" else "^${subIdentifier!!.joinToString(".")}"
}
BaseDataType.ARRAY_POINTER -> {
if(sub!=null) "^${sub.name.lowercase()}[] (split)" else "^${subIdentifier!!.joinToString(".")}[] (split)"
}
else -> base.name.lowercase()
}
@@ -160,6 +186,12 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
BaseDataType.LONG -> "long"
BaseDataType.FLOAT -> "float"
BaseDataType.STR -> "str"
BaseDataType.POINTER -> {
if(sub!=null) "^${sub.name.lowercase()}" else "^${subIdentifier!!.joinToString(".")}"
}
BaseDataType.ARRAY_POINTER -> {
if(sub!=null) "^${sub.name.lowercase()}[" else "^${subIdentifier!!.joinToString(".")}["
}
BaseDataType.ARRAY -> {
when(sub) {
BaseDataType.UBYTE -> "ubyte["
@@ -193,12 +225,20 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
BaseDataType.FLOAT -> targetType.base in arrayOf(BaseDataType.FLOAT)
BaseDataType.STR -> targetType.base in arrayOf(BaseDataType.STR, BaseDataType.UWORD)
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in arrayOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
BaseDataType.POINTER, BaseDataType.ARRAY_POINTER -> {
when {
targetType.base == BaseDataType.UWORD || targetType.base == BaseDataType.LONG -> true
targetType.isPointer -> this.isUnsignedWord
else -> false
}
}
BaseDataType.UNDEFINED -> false
}
fun largerSizeThan(other: DataType): Boolean = base.largerSizeThan(other.base)
fun equalsSize(other: DataType): Boolean = base.equalsSize(other.base)
val isBasic = sub==null && subIdentifier==null
val isUndefined = base == BaseDataType.UNDEFINED
val isByte = base.isByte
val isUnsignedByte = base == BaseDataType.UBYTE
@@ -214,6 +254,8 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
val isSigned = base.isSigned
val isUnsigned = !base.isSigned
val isArray = base.isArray
val isPointer = base.isPointer
val isPointerArray = base.isPointerArray
val isBoolArray = base.isArray && sub == BaseDataType.BOOL
val isByteArray = base.isArray && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE)
val isUnsignedByteArray = base.isArray && sub == BaseDataType.UBYTE
@@ -7,7 +7,9 @@ import prog8.code.core.IMemSizer
internal class NormalMemSizer(val floatsize: Int): IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
if(dt.isPointerArray)
return 2 * numElements!! // array of pointers is just array of uwords
else if(dt.isArray) {
if(numElements==null) return 2 // treat it as a pointer size
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
@@ -26,6 +28,7 @@ internal class NormalMemSizer(val floatsize: Int): IMemSizer {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> floatsize * (numElements ?: 1)
dt.isLong -> throw IllegalArgumentException("long can not yet be put into memory")
dt.isPointer -> 2 // pointer is just a uword
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
else -> 2 * (numElements ?: 1)
}
+4 -1
View File
@@ -90,7 +90,9 @@ class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by Nor
}
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
if(dt.isPointerArray)
return 2 * numElements!! // array of pointers is just array of uwords
else if(dt.isArray) {
if(numElements==null) return 2 // treat it as a pointer size
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
@@ -109,6 +111,7 @@ class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by Nor
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> FLOAT_MEM_SIZE * (numElements ?: 1)
dt.isLong -> throw IllegalArgumentException("long can not yet be put into memory")
dt.isPointer -> 2 // pointer is just a uword
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
else -> 2 * (numElements ?: 1)
}
@@ -38,6 +38,7 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
is PtLabel -> if(!node.name.startsWith(GENERATED_LABEL_PREFIX)) node.name = "p8l_${node.name}" // don't prefix autogenerated labels
is PtConstant -> node.name = "p8c_${node.name}"
is PtVariable, is PtMemMapped, is PtSubroutineParameter -> node.name = "p8v_${node.name}"
is PtStructDecl -> { /* do nothing */ }
}
}
@@ -177,7 +178,7 @@ private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
else {
val newAddr = PtAddressOf(elt.position)
newAddr.children.add(elt.identifier.prefix(newAddr, st))
if(elt.arrayIndexExpr!=null)
if (elt.arrayIndexExpr != null)
newAddr.children.add(elt.arrayIndexExpr!!)
newAddr.parent = arrayValue
newValue.add(newAddr)
@@ -638,7 +639,7 @@ class AsmGen6502Internal (
is PtDefer -> throw AssemblyError("defer should have been transformed")
is PtNodeGroup -> stmt.children.forEach { translate(it) }
is PtJmpTable -> translate(stmt)
is PtNop -> {}
is PtNop, is PtStructDecl -> {}
else -> throw AssemblyError("missing asm translation for $stmt")
}
}
@@ -38,7 +38,6 @@ internal class IfElseAsmGen(private val program: PtProgram,
// use a BIT instruction to test for bit 7 or 6 set/clear
val (testBitSet, variable, bitmask) = useBIT
return translateIfBIT(stmt, jumpAfterIf, testBitSet, variable, bitmask)
return
}
val rightDt = compareCond.right.type
@@ -1596,9 +1595,9 @@ _jump jmp (${target.asmLabel})
}
}
is PtAddressOf -> {
if(left.isFromArrayElement)
if(left.isFromArrayElement) {
fallbackTranslateForSimpleCondition(stmt)
else {
} else {
val varname = if(left.identifier.type.isSplitWordArray) {
if(left.isMsbForSplitArray) left.identifier.name+"_msb" else left.identifier.name+"_lsb"
} else {
@@ -1648,9 +1647,9 @@ _jump jmp (${target.asmLabel})
}
}
is PtAddressOf -> {
if(left.isFromArrayElement)
if(left.isFromArrayElement) {
fallbackTranslateForSimpleCondition(stmt)
else {
} else {
val varname = if(left.identifier.type.isSplitWordArray) {
if(left.isMsbForSplitArray) left.identifier.name+"_msb" else left.identifier.name+"_lsb"
} else {
@@ -709,6 +709,10 @@ internal class ProgramAndVarsGen(
val numbytes = compTarget.memorySize(variable.dt, variable.length!!)
asmgen.out("${variable.name}\t.fill $numbytes")
}
dt.isPointer -> asmgen.out("${variable.name}\t.word ?") // a pointer is just an uword address
dt.isPointerArray -> {
TODO("pointers are not supported yet")
}
else -> {
throw AssemblyError("weird dt")
}
@@ -440,9 +440,9 @@ internal class AssignmentAsmGen(
is PtAddressOf -> {
val arrayDt = value.identifier.type
val sourceName =
if(value.isMsbForSplitArray)
if (value.isMsbForSplitArray)
asmgen.asmSymbolName(value.identifier) + "_msb"
else if(arrayDt.isSplitWordArray)
else if (arrayDt.isSplitWordArray)
asmgen.asmSymbolName(value.identifier) + "_lsb" // the _lsb split array comes first in memory
else
asmgen.asmSymbolName(value.identifier)
@@ -1382,10 +1382,10 @@ internal class AssignmentAsmGen(
when (right) {
is PtAddressOf -> {
var symbol = asmgen.asmVariableName(right.identifier)
if(right.isFromArrayElement) {
TODO("address-of array element $symbol at ${right.position}")
TODO("address-of array element at ${right.position}")
} else {
var symbol = asmgen.asmVariableName(right.identifier)
if(right.identifier.type.isSplitWordArray) {
symbol = if(right.isMsbForSplitArray) symbol+"_msb" else symbol+"_lsb"
}
+3 -1
View File
@@ -5,7 +5,9 @@ import prog8.code.core.*
internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
if(dt.isPointerArray)
return 2 * numElements!!
else if(dt.isArray) {
require(numElements != null)
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
@@ -98,6 +98,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun translate(ifExpr: PtIfExpression): ExpressionCodeResult {
if((ifExpr.condition as? PtPrefix)?.operator=="not")
@@ -166,37 +167,38 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
private fun translate(expr: PtAddressOf): ExpressionCodeResult {
val vmDt = irType(expr.type)
val symbol = expr.identifier.name
// note: LOAD <symbol> gets you the address of the symbol, whereas LOADM <symbol> would get you the value stored at that location
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(vmDt)
val identifier = expr.identifier
fun loadAddressOfArrayLabel(reg: Int) {
if (expr.isMsbForSplitArray) {
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = symbol + "_msb"), null)
} else if (expr.identifier.type.isSplitWordArray) {
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = identifier.name + "_msb"), null)
} else if (identifier.type.isSplitWordArray) {
// the _lsb split array comes first in memory
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = symbol + "_lsb"), null)
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = identifier.name + "_lsb"), null)
} else
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = symbol), null)
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = reg, labelSymbol = identifier.name), null)
}
val resultRegister = codeGen.registers.next(vmDt)
if(expr.isFromArrayElement) {
val indexTr = translateExpression(expr.arrayIndexExpr!!)
addToResult(result, indexTr, indexTr.resultReg, -1)
val indexWordReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=indexWordReg, reg2=indexTr.resultReg), null)
if(expr.identifier.type.isUnsignedWord) {
if(identifier.type.isUnsignedWord) {
require(!expr.isMsbForSplitArray)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
it += IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = identifier.name)
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=resultRegister, reg2=indexWordReg)
}
} else {
val eltSize = codeGen.program.memsizer.memorySize(expr.identifier.type, 1)
val eltSize = codeGen.program.memsizer.memorySize(identifier.type, 1)
result += IRCodeChunk(null, null).also {
loadAddressOfArrayLabel(resultRegister)
if(eltSize>1 && !expr.identifier.type.isSplitWordArray) {
if(eltSize>1 && !identifier.type.isSplitWordArray) {
it += IRInstruction(Opcode.MUL, IRDataType.WORD, reg1=indexWordReg, immediate = eltSize)
}
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=resultRegister, reg2=indexWordReg)
@@ -506,6 +508,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.FTOUW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
}
BaseDataType.POINTER -> {
actualResultReg2 = tr.resultReg
}
else -> throw AssemblyError("weird cast value type")
}
}
@@ -549,6 +554,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else -> throw AssemblyError("weird cast value type")
}
}
BaseDataType.POINTER -> {
require(valueDt.isUnsignedWord || valueDt.isPointer)
actualResultReg2 = tr.resultReg
// no further conversion required, pointers are all just uwords
}
BaseDataType.ARRAY_POINTER -> {
TODO("typecast to array of pointers $valueDt -> ${cast.type}")
}
else -> throw AssemblyError("weird cast type")
}
@@ -259,6 +259,7 @@ class IRCodeGen(
is PtDefer -> throw AssemblyError("should have been transformed")
is PtString -> throw AssemblyError("should not occur as separate statement node ${node.position}")
is PtSub -> throw AssemblyError("nested subroutines should have been flattened ${node.position}")
is PtStructDecl -> emptyList()
else -> TODO("missing codegen for $node")
}
@@ -1885,6 +1886,7 @@ class IRCodeGen(
}
}
}
is PtStructDecl -> { /* do nothing */ }
else -> TODO("weird block child node $child")
}
}
+3 -1
View File
@@ -3,7 +3,9 @@ import prog8.code.core.*
internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray || dt.isSplitWordArray) {
if(dt.isPointerArray)
return 2 * numElements!!
else if(dt.isArray || dt.isSplitWordArray) {
require(numElements!=null)
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
@@ -390,7 +390,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val loopvar = forLoop.loopVar.targetVarDecl() ?: return noModifications
val stepLiteral = iterableRange.step as? NumericLiteral
require(loopvar.datatype.sub == null)
require(loopvar.datatype.isBasic)
val loopvarSimpleDt = loopvar.datatype.base
when(loopvarSimpleDt) {
BaseDataType.UBYTE -> {
@@ -18,8 +18,8 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
// try to statically convert a literal value into one of the desired type
val literal = typecast.expression as? NumericLiteral
if (literal != null) {
val newLiteral = literal.cast(typecast.type, typecast.implicit)
if (literal != null && typecast.type.isBasic) {
val newLiteral = literal.cast(typecast.type.base, typecast.implicit)
if (newLiteral.isValid && newLiteral.valueOrZero() !== literal) {
mods += IAstModification.ReplaceNode(typecast, newLiteral.valueOrZero(), parent)
}
@@ -33,7 +33,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
}
} else {
if (typecast.expression.inferType(program) issimpletype typecast.type) {
if (typecast.expression.inferType(program) istype typecast.type) {
// remove duplicate cast
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
}
@@ -499,7 +499,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
else if (valueDt issimpletype BaseDataType.BYTE) {
// useless lsb() of byte value, but as lsb() returns unsigned, we have to cast now.
val cast = TypecastExpression(arg.expression, BaseDataType.UBYTE, true, arg.position)
val cast = TypecastExpression(arg.expression, DataType.UBYTE, true, arg.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
}
} else {
@@ -516,7 +516,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
}
else if (argDt issimpletype BaseDataType.BYTE) {
// useless lsb() of byte value, but as lsb() returns unsigned, we have to cast now.
val cast = TypecastExpression(arg, BaseDataType.UBYTE, true, arg.position)
val cast = TypecastExpression(arg, DataType.UBYTE, true, arg.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
}
}
@@ -555,7 +555,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(functionCallExpr.target.nameInSource == listOf("mkword")) {
if(functionCallExpr.args[0].constValue(program)?.number==0.0) {
// just cast the lsb to uword
val cast = TypecastExpression(functionCallExpr.args[1], BaseDataType.UWORD, true, functionCallExpr.position)
val cast = TypecastExpression(functionCallExpr.args[1], DataType.UWORD, true, functionCallExpr.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
}
}
@@ -711,9 +711,9 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
// just use: msb(value) as type
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
return if(leftDt.isSignedWord)
TypecastExpression(msb, BaseDataType.BYTE, true, expr.position)
TypecastExpression(msb, DataType.BYTE, true, expr.position)
else
TypecastExpression(msb, BaseDataType.UWORD, true, expr.position)
TypecastExpression(msb, DataType.UWORD, true, expr.position)
}
else -> return null
}
@@ -839,14 +839,14 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
// shift left by 8 bits is just a byte operation: mkword(lsb(X), 0)
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
val mkword = FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteral(BaseDataType.UBYTE, 0.0, expr.position)), expr.position)
return TypecastExpression(mkword, BaseDataType.WORD, true, expr.position)
return TypecastExpression(mkword, DataType.WORD, true, expr.position)
}
else if (amount > 8) {
// same as above but with residual shifts.
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
val shifted = BinaryExpression(lsb, "<<", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position)
val mkword = FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position)
return TypecastExpression(mkword, BaseDataType.WORD, true, expr.position)
return TypecastExpression(mkword, DataType.WORD, true, expr.position)
}
}
else -> {
@@ -887,12 +887,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
else if(amount==8) {
// shift right by 8 bits is just a byte operation: msb(X) as uword
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
return TypecastExpression(msb, BaseDataType.UWORD, true, expr.position)
return TypecastExpression(msb, DataType.UWORD, true, expr.position)
}
else if (amount > 8) {
// same as above but with residual shifts.
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position), BaseDataType.UWORD, true, expr.position)
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)
}
}
BaseDataType.WORD -> {
@@ -903,12 +903,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
else if(amount == 8) {
// shift right by 8 bits is just a byte operation: msb(X) as byte (will get converted to word later)
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
return TypecastExpression(msb, BaseDataType.BYTE, true, expr.position)
return TypecastExpression(msb, DataType.BYTE, true, expr.position)
}
else if(amount > 8) {
// same as above but with residual shifts. Take care to do signed shift.
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
val signed = TypecastExpression(msb, BaseDataType.BYTE, true, expr.position)
val signed = TypecastExpression(msb, DataType.BYTE, true, expr.position)
return BinaryExpression(signed, ">>", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position)
}
}
@@ -115,6 +115,12 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
else -> NumericLiteral(BaseDataType.UBYTE, program.memsizer.memorySize(dt.getOrUndef(), null).toDouble(), position)
}
} else {
// the argument could refer to a struct declaration
val struct = (args[0] as? IdentifierReference)?.targetStructDecl()
if(struct!=null) {
val size = struct.memsize(program.memsizer)
return NumericLiteral(BaseDataType.UBYTE, size.toDouble(), position)
}
throw SyntaxError("sizeof invalid argument type", position)
}
}
@@ -297,6 +297,7 @@ internal class AstChecker(private val program: Program,
is Directive,
is Label,
is VarDecl,
is StructDecl,
is InlineAssembly,
is IStatementContainer -> true
is Assignment -> {
@@ -1336,10 +1337,16 @@ internal class AstChecker(private val program: Program,
errors.err("this expression doesn't return a value", typecast.expression.position)
if(typecast.expression is NumericLiteral) {
val castResult = (typecast.expression as NumericLiteral).cast(typecast.type, typecast.implicit)
if(castResult.isValid)
throw FatalAstException("cast should have been performed in const eval already")
errors.err(castResult.whyFailed!!, typecast.expression.position)
if(typecast.type.isBasic) {
val castResult = (typecast.expression as NumericLiteral).cast(typecast.type.base, typecast.implicit)
if (castResult.isValid)
throw FatalAstException("cast should have been performed in const eval already")
errors.err(castResult.whyFailed!!, typecast.expression.position)
} else if (typecast.type.isPointer) {
if(!(typecast.expression.inferType(program) istype DataType.UWORD))
errors.err("can only cast uword to pointer", typecast.position)
} else
errors.err("invalid type cast", typecast.position)
}
super.visit(typecast)
@@ -1771,6 +1778,12 @@ internal class AstChecker(private val program: Program,
errors.err("%asm containing IR code cannot be translated to 6502 assembly", inlineAssembly.position)
}
override fun visit(struct: StructDecl) {
val uniqueFields = struct.members.map { it.second }.toSet()
if(uniqueFields.size!=struct.members.size)
errors.err("duplicate field names in struct", struct.position)
}
private fun checkLongType(expression: Expression) {
if(expression.inferType(program) issimpletype BaseDataType.LONG) {
if((expression.parent as? VarDecl)?.type!=VarDeclType.CONST) {
@@ -1955,6 +1968,9 @@ internal class AstChecker(private val program: Program,
targetDt.isArray -> {
return checkValueTypeAndRange(targetDt.elementType(), value)
}
targetDt.isPointer -> {
return value.type==BaseDataType.UWORD
}
else -> return err("type of value ${value.type.toString().lowercase()} doesn't match target $targetDt")
}
return true
@@ -1966,9 +1982,9 @@ internal class AstChecker(private val program: Program,
is NumericLiteral -> it.number.toInt()
is AddressOf -> it.identifier.nameInSource.hashCode() and 0xffff
is IdentifierReference -> it.nameInSource.hashCode() and 0xffff
is TypecastExpression -> {
is TypecastExpression if it.type.isBasic -> {
val constVal = it.expression.constValue(program)
val cast = constVal?.cast(it.type, true)
val cast = constVal?.cast(it.type.base, true)
if(cast==null || !cast.isValid)
-9999999
else
@@ -2051,6 +2067,13 @@ internal class AstChecker(private val program: Program,
else if(targetDatatype.isUnsignedWord && sourceDatatype.isPassByRef) {
// this is allowed: a pass-by-reference datatype into an uword (pointer value).
}
else if (targetDatatype.isPointer) {
if(sourceDatatype.isPointer) {
if(sourceDatatype!=targetDatatype)
errors.err("cannot assign different pointer type", position)
} else if(!sourceDatatype.isUnsignedWord)
errors.err("can only assign uword or correct pointer type to a pointer", position)
}
else if(targetDatatype.isString && sourceDatatype.isUnsignedWord)
errors.err("can't assign uword to str. If the source is a string pointer and you actually want to overwrite the target string, use an explicit strings.copy(src,tgt) instead.", position)
else
@@ -9,7 +9,6 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.BaseDataType
import prog8.code.core.IErrorReporter
import prog8.code.core.isByte
import prog8.code.core.isWord
@@ -29,11 +28,11 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
}
}
if(typecast.type==sourceDt.base)
if(typecast.type==sourceDt)
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
if(sourceDt.isPassByRef) {
if(typecast.type == BaseDataType.UWORD) {
if(typecast.type.isUnsignedWord) {
val identifier = typecast.expression as? IdentifierReference
if(identifier!=null) {
return if(identifier.isSubroutineParameter()) {
@@ -5,10 +5,7 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.BaseDataType
import prog8.code.core.ComparisonOperators
import prog8.code.core.IErrorReporter
import prog8.code.core.Position
import prog8.code.core.*
internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
@@ -207,7 +204,7 @@ _after:
val indexExpr = arrayIndexedExpression.indexer.indexExpr
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl()
if(arrayVar!=null && arrayVar.datatype.isUnsignedWord) {
val wordIndex = TypecastExpression(indexExpr, BaseDataType.UWORD, true, indexExpr.position)
val wordIndex = TypecastExpression(indexExpr, DataType.UWORD, true, indexExpr.position)
val address = BinaryExpression(arrayIndexedExpression.arrayvar.copy(), "+", wordIndex, arrayIndexedExpression.position)
return if(parent is AssignTarget) {
// assignment to array
@@ -281,7 +278,9 @@ _after:
if(addressOf!=null && offset==1) {
val variable = addressOf.identifier.targetVarDecl()
if(variable!=null && variable.datatype.isWord) {
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), memread.position), mutableListOf(addressOf.identifier), memread.position)
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), memread.position), mutableListOf(
addressOf.identifier
), memread.position)
return listOf(IAstModification.ReplaceNode(memread, msb, parent))
}
}
@@ -74,8 +74,10 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
}
is UntilLoop -> throw FatalAstException("until loops must have been converted to jumps")
is VarDecl -> transform(statement)
is StructDecl -> transform(statement)
is When -> transform(statement)
is WhileLoop -> throw FatalAstException("while loops must have been converted to jumps")
is StructFieldRef -> throw FatalAstException("should not occur as part of the actual AST")
}
}
@@ -180,14 +182,12 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
private fun transform(srcTarget: AssignTarget): PtAssignTarget {
val target = PtAssignTarget(srcTarget.void, srcTarget.position)
if(srcTarget.identifier!=null)
target.add(transform(srcTarget.identifier!!))
else if(srcTarget.arrayindexed!=null)
target.add(transform(srcTarget.arrayindexed!!))
else if(srcTarget.memoryAddress!=null)
target.add(transform(srcTarget.memoryAddress!!))
else if(!srcTarget.void)
throw FatalAstException("invalid AssignTarget")
when {
srcTarget.identifier!=null -> target.add(transform(srcTarget.identifier!!))
srcTarget.arrayindexed!=null -> target.add(transform(srcTarget.arrayindexed!!))
srcTarget.memoryAddress!=null -> target.add(transform(srcTarget.memoryAddress!!))
!srcTarget.void -> throw FatalAstException("invalid AssignTarget")
}
return target
}
@@ -588,6 +588,10 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
}
}
private fun transform(struct: StructDecl): PtStructDecl {
return PtStructDecl(struct.name, struct.members, struct.position)
}
private fun transform(srcWhen: When): PtWhen {
val w = PtWhen(srcWhen.position)
w.add(transformExpression(srcWhen.condition))
@@ -616,7 +620,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
private fun transform(src: AddressOf): PtAddressOf {
val addr = PtAddressOf(src.position, src.msb)
addr.add(transform(src.identifier))
if(src.arrayIndex!=null)
if (src.arrayIndex != null)
addr.add(transformExpression(src.arrayIndex!!.indexExpr))
return addr
}
@@ -669,14 +673,14 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
expr.add(high)
} else {
val low = PtBinaryExpression("<=", DataType.BOOL, srcCheck.position)
val lowFloat = PtTypeCast(BaseDataType.FLOAT, range.from.position)
val lowFloat = PtTypeCast(DataType.FLOAT, range.from.position)
lowFloat.add(transformExpression(range.from))
low.add(lowFloat)
low.add(x1)
expr.add(low)
val high = PtBinaryExpression("<=", DataType.BOOL, srcCheck.position)
high.add(x2)
val highFLoat = PtTypeCast(BaseDataType.FLOAT, range.to.position)
val highFLoat = PtTypeCast(DataType.FLOAT, range.to.position)
highFLoat.add(transformExpression(range.to))
high.add(highFLoat)
expr.add(high)
@@ -67,7 +67,7 @@ internal class StatementReorderer(
if (!canSkipInitializationWith0(decl)) {
// Add assignment to initialize with zero
val identifier = IdentifierReference(listOf(decl.name), decl.position)
val assignzero = Assignment(AssignTarget(identifier, null, null, null, false, decl.position),
val assignzero = Assignment(AssignTarget(identifier, null, null,null, false, decl.position),
decl.zeroElementValue(), AssignmentOrigin.VARINIT, decl.position)
return listOf(IAstModification.InsertAfter(
decl, assignzero, parent as IStatementContainer
@@ -99,7 +99,7 @@ internal class StatementReorderer(
if(target!=null && target.isArray) {
val pos = decl.value!!.position
val identifier = IdentifierReference(listOf(decl.name), pos)
val assign = Assignment(AssignTarget(identifier, null, null, null, false, pos),
val assign = Assignment(AssignTarget(identifier, null, null,null, false, pos),
decl.value!!, AssignmentOrigin.VARINIT, pos)
decl.value = null
return listOf(IAstModification.InsertAfter(
@@ -98,7 +98,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
// if(rightDt.isBytes)
// modifications += IAstModification.ReplaceNode(expr.right, TypecastExpression(expr.right, leftConstAsWord.type, true, expr.right.position), expr)
}
} else if (parent is TypecastExpression && parent.type == BaseDataType.UWORD && parent.parent is Assignment) {
} else if (parent is TypecastExpression && parent.type.isUnsignedWord && parent.parent is Assignment) {
val assign = parent.parent as Assignment
if (assign.target.inferType(program).isWords) {
modifications += IAstModification.ReplaceNode(expr.left, leftConstAsWord, expr)
@@ -131,34 +131,34 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(leftDt issimpletype BaseDataType.BYTE && (rightDt issimpletype BaseDataType.UBYTE || rightDt issimpletype BaseDataType.UWORD)) {
// cast left to unsigned
val cast = TypecastExpression(expr.left, rightDt.getOrUndef().base, true, expr.left.position)
val cast = TypecastExpression(expr.left, rightDt.getOrUndef(), true, expr.left.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
if(leftDt issimpletype BaseDataType.WORD && (rightDt issimpletype BaseDataType.UBYTE || rightDt issimpletype BaseDataType.UWORD)) {
// cast left to unsigned word. Cast right to unsigned word if it is ubyte
val mods = mutableListOf<IAstModification>()
val cast = TypecastExpression(expr.left, BaseDataType.UWORD, true, expr.left.position)
val cast = TypecastExpression(expr.left, DataType.UWORD, true, expr.left.position)
mods += IAstModification.ReplaceNode(expr.left, cast, expr)
if(rightDt issimpletype BaseDataType.UBYTE) {
mods += IAstModification.ReplaceNode(expr.right,
TypecastExpression(expr.right, BaseDataType.UWORD, true, expr.right.position),
TypecastExpression(expr.right, DataType.UWORD, true, expr.right.position),
expr)
}
return mods
}
if(rightDt issimpletype BaseDataType.BYTE && (leftDt issimpletype BaseDataType.UBYTE || leftDt issimpletype BaseDataType.UWORD)) {
// cast right to unsigned
val cast = TypecastExpression(expr.right, leftDt.getOrUndef().base, true, expr.right.position)
val cast = TypecastExpression(expr.right, leftDt.getOrUndef(), true, expr.right.position)
return listOf(IAstModification.ReplaceNode(expr.right, cast, expr))
}
if(rightDt issimpletype BaseDataType.WORD && (leftDt issimpletype BaseDataType.UBYTE || leftDt issimpletype BaseDataType.UWORD)) {
// cast right to unsigned word. Cast left to unsigned word if it is ubyte
val mods = mutableListOf<IAstModification>()
val cast = TypecastExpression(expr.right, BaseDataType.UWORD, true, expr.right.position)
val cast = TypecastExpression(expr.right, DataType.UWORD, true, expr.right.position)
mods += IAstModification.ReplaceNode(expr.right, cast, expr)
if(leftDt issimpletype BaseDataType.UBYTE) {
mods += IAstModification.ReplaceNode(expr.left,
TypecastExpression(expr.left, BaseDataType.UWORD, true, expr.left.position),
TypecastExpression(expr.left, DataType.UWORD, true, expr.left.position),
expr)
}
return mods
@@ -296,7 +296,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// warn about any implicit type casts to Float, because that may not be intended
if(typecast.implicit && typecast.type==BaseDataType.FLOAT) {
if(typecast.implicit && typecast.type.isFloat) {
if(options.floats)
errors.info("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position)
else
@@ -556,7 +556,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
private fun addTypecastOrCastedValueModification(
modifications: MutableList<IAstModification>,
expressionToCast: Expression,
requiredType: BaseDataType,
requiredType: BaseDataType, // TODO DataType?
parent: Node
) {
val sourceDt = expressionToCast.inferType(program).getOrUndef()
@@ -575,7 +575,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
return
}
}
val cast = TypecastExpression(expressionToCast, requiredType, true, expressionToCast.position)
val cast = TypecastExpression(expressionToCast, DataType.forDt(requiredType), true, expressionToCast.position)
modifications += IAstModification.ReplaceNode(expressionToCast, cast, parent)
}
}
@@ -4,29 +4,28 @@ import prog8.ast.FatalAstException
import prog8.code.ast.PtExpression
import prog8.code.ast.PtFunctionCall
import prog8.code.ast.PtTypeCast
import prog8.code.core.BaseDataType
import prog8.code.core.DataType
internal fun makePushPopFunctionCalls(value: PtExpression): Pair<PtFunctionCall, PtExpression> {
var popTypecast: BaseDataType? = null
var pushTypecast: BaseDataType? = null
var popTypecast: DataType? = null
var pushTypecast: DataType? = null
var pushWord = false
var pushFloat = false
when {
value.type.isBool -> {
pushTypecast = BaseDataType.UBYTE
popTypecast = BaseDataType.BOOL
pushTypecast = DataType.UBYTE
popTypecast = DataType.BOOL
}
value.type.isSignedByte -> {
pushTypecast = BaseDataType.UBYTE
popTypecast = BaseDataType.BYTE
pushTypecast = DataType.UBYTE
popTypecast = DataType.BYTE
}
value.type.isSignedWord -> {
pushWord = true
pushTypecast = BaseDataType.UWORD
popTypecast = BaseDataType.WORD
pushTypecast = DataType.UWORD
popTypecast = DataType.WORD
}
value.type.isUnsignedByte -> {}
value.type.isUnsignedWord -> pushWord = true
@@ -132,14 +132,17 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
if(constValue!=null)
return listOf(IAstModification.ReplaceNode(typecast, constValue, parent))
if(typecast.expression is NumericLiteral) {
val value = (typecast.expression as NumericLiteral).cast(typecast.type, typecast.implicit)
if(value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
val number = typecast.expression as? NumericLiteral
if(number!=null) {
if(typecast.type.isBasic) {
val value = number.cast(typecast.type.base, typecast.implicit)
if (value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
}
}
val sourceDt = typecast.expression.inferType(program)
if(sourceDt issimpletype typecast.type)
if(sourceDt istype typecast.type)
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
if(parent is Assignment) {
@@ -423,7 +426,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
if(valueDt.isWords)
return listOf(IAstModification.ReplaceNode(functionCallExpr, functionCallExpr.args[0], parent))
if(valueDt.isBytes) {
val cast = TypecastExpression(functionCallExpr.args[0], BaseDataType.UWORD, true, functionCallExpr.position)
val cast = TypecastExpression(functionCallExpr.args[0], DataType.UWORD, true, functionCallExpr.position)
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
}
}
+3 -3
View File
@@ -120,7 +120,7 @@ other {
test("generated constvalue from typecast inherits proper parent linkage") {
val number = NumericLiteral(BaseDataType.UBYTE, 11.0, Position.DUMMY)
val tc = TypecastExpression(number, BaseDataType.BYTE, false, Position.DUMMY)
val tc = TypecastExpression(number, DataType.BYTE, false, Position.DUMMY)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
tc.linkParents(ParentSentinel)
tc.parent shouldNotBe null
@@ -985,13 +985,13 @@ main {
funcarg2 shouldBe NumericLiteral(BaseDataType.UWORD, 8.0, Position.DUMMY)
val answer3ValueTc = (st[15] as Assignment).value as TypecastExpression
answer3ValueTc.type shouldBe BaseDataType.UWORD
answer3ValueTc.type shouldBe DataType.UWORD
val answer3Value = answer3ValueTc.expression as FunctionCallExpression
answer3Value.target.nameInSource shouldBe listOf("msb")
answer3Value.args.single() shouldBe instanceOf<BinaryExpression>()
val funcarg3tc = (st[16] as FunctionCallStatement).args.single() as TypecastExpression
funcarg3tc.type shouldBe BaseDataType.UWORD
funcarg3tc.type shouldBe DataType.UWORD
val funcarg3 = funcarg3tc.expression as FunctionCallExpression
funcarg3.target.nameInSource shouldBe listOf("msb")
funcarg3.args.single() shouldBe instanceOf<BinaryExpression>()
+5 -5
View File
@@ -227,11 +227,11 @@ main {
stmts.size shouldBe 4
val assign1tc = (stmts[2] as Assignment).value as TypecastExpression
val assign2tc = (stmts[3] as Assignment).value as TypecastExpression
assign1tc.type shouldBe BaseDataType.WORD
assign2tc.type shouldBe BaseDataType.WORD
assign1tc.type shouldBe DataType.WORD
assign2tc.type shouldBe DataType.WORD
assign2tc.expression shouldBe instanceOf<IdentifierReference>()
val assign1subtc = (assign1tc.expression as TypecastExpression)
assign1subtc.type shouldBe BaseDataType.BYTE
assign1subtc.type shouldBe DataType.BYTE
assign1subtc.expression shouldBe instanceOf<IdentifierReference>()
}
@@ -942,7 +942,7 @@ main {
(v2.right as NumericLiteral).number shouldBe 399
val v3 = (st[4] as Assignment).value as TypecastExpression
v3.type shouldBe BaseDataType.UWORD
v3.type shouldBe DataType.UWORD
val v3e = v3.expression as BinaryExpression
v3e.operator shouldBe "*"
(v3e.left as IdentifierReference).nameInSource shouldBe listOf("cx16","r0L")
@@ -952,7 +952,7 @@ main {
val v4 = (st[5] as Assignment).value as BinaryExpression
v4.operator shouldBe "*"
val v4t = v4.left as TypecastExpression
v4t.type shouldBe BaseDataType.UWORD
v4t.type shouldBe DataType.UWORD
(v4t.expression as IdentifierReference).nameInSource shouldBe listOf("cx16","r0L")
(v4.right as NumericLiteral).type shouldBe BaseDataType.UWORD
(v4.right as NumericLiteral).number shouldBe 5
+2 -2
View File
@@ -840,12 +840,12 @@ main {
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 8
val assignUbbVal = ((st[5] as Assignment).value as TypecastExpression)
assignUbbVal.type shouldBe BaseDataType.UBYTE
assignUbbVal.type shouldBe DataType.UBYTE
assignUbbVal.expression shouldBe instanceOf<IdentifierReference>()
val assignVaddr = (st[7] as Assignment).value as FunctionCallExpression
assignVaddr.target.nameInSource shouldBe listOf("mkword")
val tc = assignVaddr.args[0] as TypecastExpression
tc.type shouldBe BaseDataType.UBYTE
tc.type shouldBe DataType.UBYTE
tc.expression shouldBe instanceOf<ArrayIndexedExpression>()
}
@@ -114,7 +114,6 @@ class TestAsmGenSymbols: StringSpec({
val localvarIdentScoped = (sub.children.asSequence().filterIsInstance<PtAssignment>().first { (it.value as? PtAddressOf)?.identifier?.name=="main.start.localvar" }.value as PtAddressOf).identifier
asmgen.asmSymbolName(localvarIdentScoped) shouldBe "localvar"
asmgen.asmVariableName(localvarIdentScoped) shouldBe "localvar"
val scopedVarIdent = (sub.children.asSequence().filterIsInstance<PtAssignment>().first { (it.value as? PtAddressOf)?.identifier?.name=="main.var_outside" }.value as PtAddressOf).identifier
asmgen.asmSymbolName(scopedVarIdent) shouldBe "main.var_outside"
asmgen.asmVariableName(scopedVarIdent) shouldBe "main.var_outside"
+3 -1
View File
@@ -21,7 +21,9 @@ internal object DummyFunctions : IBuiltinFunctions {
internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray || dt.isSplitWordArray) {
if(dt.isPointerArray)
return 2 * numElements!!
else if(dt.isArray || dt.isSplitWordArray) {
require(numElements!=null)
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
@@ -155,6 +155,14 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
}
override fun visit(struct: StructDecl) {
outputln("struct ${struct.name} {")
for(member in struct.members) {
outputlni( " ${member.first} ${member.second}")
}
outputlni("}")
}
override fun visit(subroutine: Subroutine) {
output("\n")
outputi("")
@@ -454,7 +462,7 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
override fun visit(typecast: TypecastExpression) {
output("(")
typecast.expression.accept(this)
output(" as ${DataType.forDt(typecast.type).sourceString()}) ")
output(" as ${typecast.type} ")
}
override fun visit(memread: DirectMemoryRead) {
@@ -474,7 +482,7 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
if(addressOf.msb)
output(">")
addressOf.identifier.accept(this)
if(addressOf.arrayIndex!=null) {
if (addressOf.arrayIndex != null) {
output("[")
addressOf.arrayIndex?.accept(this)
output("]")
+52 -23
View File
@@ -45,6 +45,7 @@ internal fun BlockContext.toAst(isInLibrary: Boolean) : Block {
it.inlineir()!=null -> it.inlineir().toAst()
it.labeldef()!=null -> it.labeldef().toAst()
it.alias()!=null -> it.alias().toAst()
it.structdeclaration()!=null -> it.structdeclaration().toAst()
else -> throw FatalAstException("weird block node $it")
}
}
@@ -54,7 +55,7 @@ internal fun BlockContext.toAst(isInLibrary: Boolean) : Block {
private fun Statement_blockContext.toAst(): MutableList<Statement> =
statement().asSequence().map { it.toAst() }.toMutableList()
private fun VariabledeclarationContext.toAst() : Statement {
private fun VariabledeclarationContext.toAst() : VarDecl {
vardecl()?.let {
return it.toAst(VarDeclType.VAR, null)
}
@@ -76,6 +77,12 @@ private fun VariabledeclarationContext.toAst() : Statement {
throw FatalAstException("weird variable decl $this")
}
private fun StructdeclarationContext.toAst(): Statement {
val name = identifier().text
val members = structfielddecl().map { it.toAst() }
return StructDecl(name, members, toPosition())
}
private fun SubroutinedeclarationContext.toAst() : Subroutine {
return when {
subroutine()!=null -> subroutine().toAst()
@@ -165,6 +172,9 @@ private fun StatementContext.toAst() : Statement {
val aliasstmt = alias()?.toAst()
if(aliasstmt!=null) return aliasstmt
val structdecl = structdeclaration()?.toAst()
if(structdecl!=null) return structdecl
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
}
@@ -231,18 +241,12 @@ private fun Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
else -> throw SyntaxError("invalid register or status flag", toPosition())
}
}
// asmsubs currently only return a base datatype
val returnBaseDt = it.datatype().toAst()
AsmSubroutineReturn(
DataType.forDt(returnBaseDt),
registerorpair,
statusregister)
AsmSubroutineReturn(it.datatype().toAst(), registerorpair, statusregister)
}
private fun Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter> = asmsub_param().map {
val vardecl = it.vardecl()
val baseDt = vardecl.datatype()?.toAst() ?: BaseDataType.UNDEFINED
var datatype = DataType.forDt(baseDt)
var datatype = vardecl.datatype()?.toAst() ?: DataType.UNDEFINED
if(vardecl.ARRAYSIG()!=null || vardecl.arrayindex()!=null)
datatype = datatype.elementToArray()
val (registerorpair, statusregister) = parseParamRegister(it.register, it.toPosition())
@@ -319,7 +323,7 @@ private fun SubroutineContext.toAst() : Subroutine {
return Subroutine(
identifier().text,
sub_params()?.toAst()?.toMutableList() ?: mutableListOf(),
returntypes.map { DataType.forDt(it) }.toMutableList(),
returntypes.toMutableList(),
emptyList(),
emptyList(),
emptySet(),
@@ -341,8 +345,7 @@ private fun Sub_paramsContext.toAst(): List<SubroutineParameter> =
throw SyntaxError("invalid parameter tag '$tag'", toPosition())
}
val zp = getZpOption(tags)
val baseDt = decl.datatype()?.toAst() ?: BaseDataType.UNDEFINED
var datatype = DataType.forDt(baseDt)
var datatype = decl.datatype()?.toAst() ?: DataType.UNDEFINED
if(decl.ARRAYSIG()!=null || decl.arrayindex()!=null)
datatype = datatype.elementToArray()
@@ -380,16 +383,16 @@ private fun Assign_targetContext.toAst() : AssignTarget {
AssignTarget(identifier, null, null, null, false, scoped_identifier().toPosition())
}
is MemoryTargetContext ->
AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), directmemory().toPosition()), null, false, toPosition())
AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), directmemory().toPosition()),null,false, toPosition())
is ArrayindexedTargetContext -> {
val ax = arrayindexed()
val arrayvar = ax.scoped_identifier().toAst()
val index = ax.arrayindex().toAst()
val arrayindexed = ArrayIndexedExpression(arrayvar, index, ax.toPosition())
AssignTarget(null, arrayindexed, null, null, false, toPosition())
AssignTarget(null, arrayindexed, null,null,false, toPosition())
}
is VoidTargetContext -> {
AssignTarget(null, null, null, null, true, void_().toPosition())
AssignTarget(null, null, null,null,true, void_().toPosition())
}
else -> throw FatalAstException("weird assign target node $this")
}
@@ -397,7 +400,7 @@ private fun Assign_targetContext.toAst() : AssignTarget {
private fun Multi_assign_targetContext.toAst() : AssignTarget {
val targets = this.assign_target().map { it.toAst() }
return AssignTarget(null, null, null, targets, false, toPosition())
return AssignTarget(null, null, null,targets, false, toPosition())
}
private fun ClobberContext.toAst() : Set<CpuRegister> {
@@ -430,7 +433,7 @@ private fun AugassignmentContext.toAst(): Assignment {
return Assignment(assign_target().toAst(), expression, AssignmentOrigin.USERCODE, toPosition())
}
private fun DatatypeContext.toAst(): BaseDataType {
private fun BasedatatypeContext.toAst(): BaseDataType {
return try {
BaseDataType.valueOf(text.uppercase())
} catch (_: IllegalArgumentException) {
@@ -438,6 +441,22 @@ private fun DatatypeContext.toAst(): BaseDataType {
}
}
private fun DatatypeContext.toAst(): DataType {
val base = basedatatype()?.toAst()
if(base!=null)
return DataType.forDt(base)
val pointer = pointertype().toAst()
return pointer
}
private fun PointertypeContext.toAst(): DataType {
val base = basedatatype()?.toAst()
if(base!=null)
return DataType.pointer(base)
val identifier = scoped_identifier().identifier().map { it.text}
return DataType.pointer(identifier)
}
private fun ArrayindexContext.toAst() : ArrayIndex =
ArrayIndex(expression().toAst(), toPosition())
@@ -589,9 +608,8 @@ private fun ExpressionContext.toAst(insideParentheses: Boolean=false) : Expressi
return expression(0).toAst(insideParentheses=true) // expression within ( )
if(typecast()!=null) {
// typecast is always to a base datatype
val baseDt = typecast().datatype().toAst()
return TypecastExpression(expression(0).toAst(), baseDt, false, toPosition())
val dt = typecast().datatype().toAst()
return TypecastExpression(expression(0).toAst(), dt, false, toPosition())
}
if(directmemory()!=null)
@@ -603,7 +621,7 @@ private fun ExpressionContext.toAst(insideParentheses: Boolean=false) : Expressi
val msb = addressOf.ADDRESS_OF_MSB()!=null
// note: &< (ADDRESS_OF_LSB) is equivalent to a regular &.
return if (identifier != null)
AddressOf(addressof().scoped_identifier().toAst(), null, msb, toPosition())
AddressOf(addressof().scoped_identifier().toAst(),null, msb, toPosition())
else {
val array = addressOf.arrayindexed()
AddressOf(array.scoped_identifier().toAst(), array.arrayindex().toAst(), msb, toPosition())
@@ -772,6 +790,12 @@ private fun When_choiceContext.toAst(): WhenChoice {
return WhenChoice(values?.toMutableList(), scope, toPosition())
}
private fun StructfielddeclContext.toAst(): Pair<DataType, String> {
val identifier = identifier().NAME().text
val dt = datatype().toAst()
return dt to identifier
}
private fun VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl {
val tags = TAG().map { it.text }
val validTags = arrayOf("@zp", "@requirezp", "@nozp", "@split", "@nosplit", "@shared", "@alignword", "@alignpage", "@align64", "@dirty")
@@ -790,8 +814,13 @@ private fun VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl
val alignpage = "@alignpage" in tags
if(alignpage && alignword)
throw SyntaxError("choose a single alignment option", toPosition())
val baseDt = datatype()?.toAst() ?: BaseDataType.UNDEFINED
val dt = if(isArray) DataType.arrayFor(baseDt, split!=SplitWish.NOSPLIT) else DataType.forDt(baseDt)
val baseDt = datatype()?.toAst() ?: DataType.UNDEFINED
val dt = if(!isArray) baseDt else {
if(baseDt.isPointer)
DataType.arrayOfPointersTo(baseDt.sub, baseDt.subIdentifier)
else
DataType.arrayFor(baseDt.base, split!=SplitWish.NOSPLIT)
}
return VarDecl(
type, VarDeclOrigin.USERCODE,
@@ -70,10 +70,10 @@ sealed class Expression: Node {
if(sourceDt.base==targetDt && sourceDt.sub==null)
return Pair(false, this)
if(this is TypecastExpression) {
this.type = targetDt
this.type = DataType.forDt(targetDt)
return Pair(false, this)
}
val typecast = TypecastExpression(this, targetDt, implicit, this.position)
val typecast = TypecastExpression(this, DataType.forDt(targetDt), implicit, this.position)
return Pair(true, typecast)
}
}
@@ -369,7 +369,7 @@ class ArrayIndexedExpression(var arrayvar: IdentifierReference,
override fun copy() = ArrayIndexedExpression(arrayvar.copy(), indexer.copy(), position)
}
class TypecastExpression(var expression: Expression, var type: BaseDataType, val implicit: Boolean, override val position: Position) : Expression() {
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) {
@@ -378,7 +378,7 @@ class TypecastExpression(var expression: Expression, var type: BaseDataType, val
}
init {
if(type==BaseDataType.BOOL) require(!implicit) {"no implicit cast to boolean allowed"}
if(type.isBool) require(!implicit) {"no implicit cast to boolean allowed"}
}
override val isSimple = expression.isSimple
@@ -396,9 +396,11 @@ class TypecastExpression(var expression: Expression, var type: BaseDataType, val
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, implicit)
val cast = cv.cast(type.base, implicit)
return if(cast.isValid) {
val newval = cast.valueOrZero()
newval.linkParents(parent)
@@ -425,17 +427,21 @@ data class AddressOf(var identifier: IdentifierReference, var arrayIndex: ArrayI
override val isSimple = true
override fun replaceChildNode(node: Node, replacement: Node) {
if(node===identifier) {
require(replacement is IdentifierReference)
identifier = replacement
replacement.parent = this
} else if(node===arrayIndex) {
require(replacement is ArrayIndex)
arrayIndex = replacement
replacement.parent = this
} else {
throw FatalAstException("invalid replace, no child node $node")
when {
node===identifier -> {
require(replacement is IdentifierReference)
identifier = replacement
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(), msb, position)
@@ -1164,7 +1170,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
override val isSimple = true
fun targetStatement(program: Program?) =
fun targetStatement(program: Program?): Statement? =
if(program!=null && nameInSource.singleOrNull() in program.builtinFunctions.names)
BuiltinFunctionPlaceholder(nameInSource[0], position, parent)
else
@@ -1172,6 +1178,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
fun targetVarDecl(): VarDecl? = targetStatement(null) as? VarDecl
fun targetSubroutine(): Subroutine? = targetStatement(null) as? Subroutine
fun targetStructDecl(): StructDecl? = targetStatement(null) as? StructDecl
fun targetNameAndType(program: Program): Pair<String, DataType> {
val target = targetStatement(program) as? INamedStatement ?: throw FatalAstException("can't find target for $nameInSource")
@@ -1251,6 +1258,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
}
}
class FunctionCallExpression(override var target: IdentifierReference,
override val args: MutableList<Expression>,
override val position: Position) : Expression(), IFunctionCall {
@@ -1437,7 +1445,7 @@ class IfExpression(var condition: Expression, var truevalue: Expression, var fal
override fun replaceChildNode(node: Node, replacement: Node) {
if(replacement !is Expression)
throw throw FatalAstException("invalid replace")
throw FatalAstException("invalid replace")
if(node===condition) condition=replacement
else if(node===truevalue) truevalue=replacement
else if(node===falsevalue) falsevalue=replacement
@@ -21,7 +21,7 @@ object InferredTypes {
false
else if(type==BaseDataType.STR && this.datatype?.base==BaseDataType.STR)
true
else (this.datatype?.base == type && this.datatype.sub == null) // strict equality if known
else (this.datatype?.base == type && this.datatype.isBasic) // strict equality if known
companion object {
fun unknown() = InferredType(isUnknown = true, isVoid = false, datatype = null)
@@ -82,6 +82,13 @@ object InferredTypes {
type.isFloat -> InferredType.known(BaseDataType.FLOAT)
type.isString -> InferredType.known(BaseDataType.STR)
type.isLong -> InferredType.known(BaseDataType.LONG)
type.isPointerArray -> InferredType.known(DataType.arrayOfPointersTo(type.sub, type.subIdentifier))
type.isPointer -> {
if(type.sub!=null)
InferredType.known(DataType.pointer(type.sub!!))
else
InferredType.known(DataType.pointer(type.subIdentifier!!))
}
type.isSplitWordArray -> {
when(type.sub) {
BaseDataType.UWORD -> InferredType.known(DataType.arrayFor(BaseDataType.UWORD))
@@ -374,6 +374,42 @@ class VarDecl(val type: VarDeclType,
}
}
class StructDecl(override val name: String, val members: List<Pair<DataType, String>>, override val position: Position) : Statement(), INamedStatement {
override lateinit var parent: Node
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 copy() = StructDecl(name, members.toList(), position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
fun memsize(sizer: IMemSizer): Int = members.sumOf { sizer.memorySize(it.first, 1) }
fun getField(name: String): Pair<DataType, String>? = members.firstOrNull { it.second==name }
}
class StructFieldRef(val pointer: IdentifierReference, val struct: StructDecl, val type: DataType, override val name: String, override val position: Position): Statement(), INamedStatement {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
// pointer and struct are not our property!
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun referencesIdentifier(nameInSource: List<String>) = pointer.referencesIdentifier(nameInSource) || struct.referencesIdentifier(nameInSource)
override fun copy(): StructFieldRef = StructFieldRef(pointer.copy(), struct.copy(), type, name, position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class ArrayIndex(var indexExpr: Expression,
override val position: Position) : Node {
override lateinit var parent: Node
@@ -545,8 +581,15 @@ data class AssignTarget(var identifier: IdentifierReference?,
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node === identifier -> identifier = replacement as IdentifierReference
node === arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression
node === identifier -> {
require(replacement is IdentifierReference)
identifier = replacement
arrayindexed = null
}
node === arrayindexed -> {
arrayindexed = replacement as ArrayIndexedExpression
identifier = null
}
node === multi -> throw FatalAstException("can't replace multi assign targets")
else -> throw FatalAstException("invalid replace")
}
@@ -105,6 +105,8 @@ abstract class AstWalker {
open fun before(continueStmt: Continue, parent: Node): Iterable<IAstModification> = noModifications
open fun before(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> = noModifications
open fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> = noModifications
open fun before(struct: StructDecl, parent: Node): Iterable<IAstModification> = noModifications
open fun before(field: StructFieldRef, parent: Node): Iterable<IAstModification> = noModifications
open fun before(directive: Directive, parent: Node): Iterable<IAstModification> = noModifications
open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = noModifications
@@ -150,6 +152,8 @@ abstract class AstWalker {
open fun after(continueStmt: Continue, parent: Node): Iterable<IAstModification> = noModifications
open fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> = noModifications
open fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> = noModifications
open fun after(struct: StructDecl, parent: Node): Iterable<IAstModification> = noModifications
open fun after(field: StructFieldRef, parent: Node): Iterable<IAstModification> = noModifications
open fun after(directive: Directive, parent: Node): Iterable<IAstModification> = noModifications
open fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = noModifications
@@ -269,6 +273,16 @@ abstract class AstWalker {
track(after(decl, parent), decl, parent)
}
fun visit(struct: StructDecl, parent: Node) {
track(before(struct, parent), struct, parent)
track(after(struct, parent), struct, parent)
}
fun visit(field: StructFieldRef, parent: Node) {
track(before(field, parent), field, parent)
track(after(field, parent), field, parent)
}
fun visit(subroutine: Subroutine, parent: Node) {
track(before(subroutine, parent), subroutine, parent)
subroutine.asmAddress?.varbank?.accept(this, subroutine)
@@ -44,6 +44,12 @@ interface IAstVisitor {
decl.arraysize?.accept(this)
}
fun visit(struct: StructDecl) {
}
fun visit(field: StructFieldRef) {
}
fun visit(subroutine: Subroutine) {
subroutine.asmAddress?.varbank?.accept(this)
subroutine.statements.forEach { it.accept(this) }
+25 -2
View File
@@ -1,14 +1,37 @@
TODO
====
STRUCTS and TYPED POINTERS
--------------------------
...
- add ast check for assignments to struct fields type checks node_ptr.nextnode = enemy_ptr
- add IR LOADPIX/STOREPIX instructions for efficient field access through a pointer var
- DONE: declare struct as a separate entity so you can then declare multiple variables (pointers) of the same struct type. Like usual.
- struct is a 'packed' struct, fields are placed in order of declaration. This guarantees exact size and place of the fields
- structs only supported as a reference type (uword pointer). This removes a lot of the problems related to introducing a variable length value type.
- need to introduce typed pointer datatype in prog8 to allow this to make any sense. + correct code gen
- initially only a pointer-to-struct should actually work, pointer-to-other-type is possible but that can come later.
- DONE: a struct can contain only numeric type fields (byte,word,float) - no nested structs, no reference types (strings, arrays) inside structs.
- DONE: struct might also contain typed pointer fields (because a pointer is just an address word)
- max 1 page of memory total size to allow regular register indexing
- DONE: assigning ptrs of different types is only allowed via a cast as usual. For simple address (uword) assignments, no cast is needed (but allowed)
- how to dereference a pointer? Pascal does it like this: ptr^
- dereferencing a pointer to struct could look like Pascal's ptr^.field as well, but the ^ is actually redundant here; compiler already knows it's a pointer type.
Note that actually dereferencing a pointer to a struct as an explicit operation, conflicts with the third axiom on this list (structs only as reference types) so it can only be done for basic types?
So... setting struct fields can simply be ``structvar.field = 42`` and reading them ``a = structvar.field``
- DONE: you should be able to get the address of an individual field: ``&structpointer.field``
- arrays of structs? Just an array of uword pointers to said structs. Can even be @split as the only representation form because that's the default for word arrays.
- DONE: need to teach sizeof() how to calculate struct sizes (need unit test + doc)
- static initialization of structs may be allowed only at block scope and then behaves like arrays; it won't reset to the original value when program is restarted, so beware. Syntax = TBD
- allow memory-mapped structs? Something like &Sprite sprite0 = $9000 basically behaves identically to a typed pointer, but the address is immutable as usual
- existing STR and ARRAY remain unchanged (don't become typed pointers) so we can keep doing register-indexed addressing directly on them
- rather than str or uword parameter types for routines with a string argument, use ^str (or ^ubyte maybe? these are more or less identical..?)
- same for arrays? pointer-to-array syntax = TBD
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
- STRUCTS: are now being developed in their own separate branch "structs". This will be for the next major version of the compiler (v12)
- is "checkAssignmentCompatible" redundant (gets called just 1 time!) when we also have "checkValueTypeAndRange" ?
- romable: should we have a way to explicitly set the memory address for the BSS area (instead of only the highram bank number on X16, allow a memory address too for the -varshigh option?)
- Kotlin: can we use inline value classes in certain spots?
+71 -7
View File
@@ -1,15 +1,79 @@
%zeropage basicsafe
%import textio
%import strings
%import floats
%zeropage basicsafe
%option no_sysinit
main {
sub start() {
str name1 = sc:"irmen de jong 123456789 the quick brown fox"
str name2 = sc:"jumps over the lazy frog"
txt.print_ub(strings.hash(name1))
struct Enemy {
ubyte x
ubyte y
uword value
float rotation
bool alive
; no strings or arrays allowed in struct type declarations.
; typed pointers are allowed though because these are just a uword:
^float floatptr
^str stringpointer
}
sub start() {
; struct declarations also allowed inside subroutine scope
struct Node {
ubyte type
uword value
^Node nextnode ; linked list?
}
; declare pointer vars
^bool @shared bool_ptr
^ubyte @shared ubyte_ptr
^word @shared word_ptr
^Node @shared node_ptr
^Enemy @shared enemy_ptr
^bool[5] @shared boolptr_list ; array of pointers to bools (bit silly, should we even support this)
^Node[5] @shared node_list ; array of pointers to nodes
^Enemy[5] @shared enemy_list ; array of pointers to enemies
txt.print("sizeofs: ")
txt.print_ub(sizeof(Enemy))
txt.spc()
txt.print_ub(strings.hash(name2))
txt.print_ub(sizeof(Node))
txt.spc()
txt.print_ub(sizeof(bool_ptr))
txt.nl()
; point to a memory address.
bool_ptr = 2000
bool_ptr = 2002 as ^bool
ubyte_ptr = 2000
word_ptr = 2000
node_ptr = 2000
enemy_ptr = 2000
bool_ptr = enemy_ptr as ^bool ; cast makes no sense, but hey, programmer knows best right? (without cast would give error)
; array elements, point to a memory address
node_list[0] = 1000
node_list[1] = 2000
node_list[1] = 2002 as ^Node
node_list[2] = 3000
; BELOW DOESN'T WORK YET:
; writing and reading fields
enemy_ptr.x = 42
enemy_ptr.alive = true
node_ptr.nextnode = 2000
node_ptr.nextnode = enemy_ptr ; TODO should give type error!
node_ptr.nextnode = node_ptr ; link to self
node_ptr.nextnode.value = 888 ; traverse multiple pointers
main.start.enemy_ptr.value = 600 ; struct ptr vars can occur anywhere in a scoped name, not just the first segment
cx16.r0 = enemy_ptr.value
; address of fields
txt.print_uw(&enemy_ptr.alive)
txt.nl()
; TODO how to statically allocate/initialize a struct? Difficult.. see TODO in docs
}
}
@@ -544,6 +544,18 @@ class IRFileReader {
private fun parseDatatype(type: String, isArray: Boolean): DataType {
if(isArray) {
if(type[0]=='^') {
return when(type.drop(1)) {
"bool" -> DataType.arrayOfPointersTo(BaseDataType.BOOL, null)
"byte" -> DataType.arrayOfPointersTo(BaseDataType.BYTE, null)
"ubyte", "str" -> DataType.arrayOfPointersTo(BaseDataType.UBYTE, null)
"word" -> DataType.arrayOfPointersTo(BaseDataType.WORD, null)
"uword" -> DataType.arrayOfPointersTo(BaseDataType.UWORD, null)
"float" -> DataType.arrayOfPointersTo(BaseDataType.FLOAT, null)
"long" -> DataType.arrayOfPointersTo(BaseDataType.LONG, null)
else -> DataType.arrayOfPointersTo(null, type.drop(1).split('.'))
}
}
return when(type) {
"bool" -> DataType.arrayFor(BaseDataType.BOOL, false)
"byte" -> DataType.arrayFor(BaseDataType.BYTE, false)
@@ -555,6 +567,20 @@ class IRFileReader {
else -> throw IRParseException("invalid dt $type")
}
} else {
if(type[0]=='^') {
// pointer type to either a base datatype, or a struct name
return when(type.drop(1)) {
"bool" -> DataType.pointer(BaseDataType.BOOL)
"byte" -> DataType.pointer(BaseDataType.BYTE)
"ubyte" -> DataType.pointer(BaseDataType.UBYTE)
"word" -> DataType.pointer(BaseDataType.WORD)
"uword" -> DataType.pointer(BaseDataType.UWORD)
"float" -> DataType.pointer(BaseDataType.FLOAT)
"long" -> DataType.pointer(BaseDataType.LONG)
// note: 'str' should not occur anymore in IR. Should be 'uword'
else -> DataType.pointer(type.drop(1).split('.'))
}
}
return when(type) {
"bool" -> DataType.BOOL
"byte" -> DataType.BYTE
+6 -1
View File
@@ -1,7 +1,7 @@
package prog8.intermediate
import prog8.code.core.*
import prog8.Either
import prog8.code.core.*
import prog8.left
import prog8.right
@@ -17,6 +17,8 @@ fun DataType.irTypeString(length: Int?): String {
BaseDataType.LONG -> "long"
BaseDataType.FLOAT -> "float"
BaseDataType.STR -> "ubyte[$lengthStr]" // here string doesn't exist as a seperate datatype anymore
BaseDataType.POINTER -> if(sub!=null) "^${sub!!.name.lowercase()}" else "^${subIdentifier!!.joinToString(".")}"
BaseDataType.ARRAY_POINTER -> if(sub!=null) "^${sub!!.name.lowercase()}[$lengthStr]" else "^${subIdentifier!!.joinToString(".")}[$lengthStr]"
BaseDataType.ARRAY -> {
when(this.sub) {
BaseDataType.UBYTE -> "ubyte[$lengthStr]"
@@ -342,6 +344,9 @@ internal fun parseRegisterOrStatusflag(sourceregs: String): RegisterOrStatusflag
fun irType(type: DataType): IRDataType {
if(type.isPointer)
return IRDataType.WORD // TODO do we need typed pointers in IR? probably not though?
if(type.base.isPassByRef)
return IRDataType.WORD
+14 -1
View File
@@ -77,6 +77,7 @@ block: identifier integerliteral? EOL? '{' EOL? (block_statement | EOL)* '}';
block_statement:
directive
| variabledeclaration
| structdeclaration
| subroutinedeclaration
| inlineasm
| inlineir
@@ -88,6 +89,7 @@ block_statement:
statement :
directive
| variabledeclaration
| structdeclaration
| assignment
| augassignment
| unconditionaljump
@@ -121,6 +123,13 @@ variabledeclaration :
;
structdeclaration:
'struct' identifier '{' EOL? (structfielddecl | EOL)+ '}'
;
structfielddecl: datatype identifier;
subroutinedeclaration :
subroutine
| asmsubroutine
@@ -153,7 +162,11 @@ constdecl: 'const' varinitializer ;
memoryvardecl: ADDRESS_OF varinitializer;
datatype: 'ubyte' | 'byte' | 'uword' | 'word' | 'long' | 'float' | 'str' | 'bool' ;
basedatatype: 'ubyte' | 'byte' | 'uword' | 'word' | 'long' | 'float' | 'str' | 'bool' ;
datatype: pointertype | basedatatype;
pointertype: '^' (scoped_identifier | basedatatype);
arrayindex: '[' expression ']' ;
+10 -1
View File
@@ -3,6 +3,7 @@ package prog8.code
import prog8.code.ast.PtAsmSub
import prog8.code.ast.PtNode
import prog8.code.ast.PtProgram
import prog8.code.ast.PtStructDecl
import prog8.code.core.*
@@ -107,7 +108,8 @@ enum class StNodeType {
MEMVAR,
CONSTANT,
BUILTINFUNC,
MEMORYSLAB
MEMORYSLAB,
STRUCT
}
@@ -249,6 +251,13 @@ class StMemVar(name: String,
}
}
class StStruct(
name: String,
val members: List<Pair<DataType, String>>,
astNode: PtStructDecl?
) : StNode(name, StNodeType.STRUCT, astNode)
class StMemorySlab(
name: String,
val size: UInt,
+7 -3
View File
@@ -60,6 +60,9 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
val params = node.parameters.map {StSubroutineParameter(it.name, it.type, it.register) }
StSub(node.name, params, node.returns, node)
}
is PtStructDecl -> {
StStruct(node.name, node.members, node)
}
is PtVariable -> {
val initialNumeric: Double?
val initialString: StString?
@@ -134,9 +137,10 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp
return value.children.map {
when(it) {
is PtAddressOf -> {
if(it.isFromArrayElement)
TODO("address-of array element $it in initial array value")
StArrayElement(null, it.identifier.name, null)
when {
it.isFromArrayElement -> TODO("address-of array element $it in initial array value")
else -> StArrayElement(null, it.identifier.name, null)
}
}
is PtNumber -> StArrayElement(it.number, null, null)
is PtBool -> StArrayElement(null, null, it.value)
@@ -25,7 +25,9 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
is PtAddressOf -> {
if(other !is PtAddressOf)
return false
if (other.type!==type || !(other.identifier isSameAs identifier))
if (other.type!==type)
return false
if(!(other.identifier isSameAs identifier))
return false
if(other.children.size!=children.size)
return false
@@ -157,7 +159,7 @@ class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(el
get() = variable.type.isSplitWordArray
init {
require(elementType.isNumericOrBool)
require(elementType.isNumericOrBool || elementType.isPointer)
}
}
@@ -376,12 +378,12 @@ class PtString(val value: String, val encoding: Encoding, position: Position) :
}
class PtTypeCast(type: BaseDataType, position: Position) : PtExpression(DataType.forDt(type), position) {
class PtTypeCast(type: DataType, position: Position) : PtExpression(type, position) {
val value: PtExpression
get() = children.single() as PtExpression
fun copy(): PtTypeCast {
val copy = PtTypeCast(type.base, position)
val copy = PtTypeCast(type, position)
if(children[0] is PtIdentifier) {
copy.add((children[0] as PtIdentifier).copy())
} else {
@@ -172,6 +172,9 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
is PtDefer -> "<defer>"
is PtIfExpression -> "<ifexpr>"
is PtJmpTable -> "<jmptable>"
is PtStructDecl -> {
"struct ${node.name} { " + node.members.joinToString(" ") { "${it.first} ${it.second}" } + " }"
}
}
}
@@ -221,6 +221,9 @@ class PtMemMapped(name: String, override val type: DataType, val address: UInt,
}
class PtStructDecl(name: String, val members: List<Pair<DataType, String>>, position: Position) : PtNamedNode(name, position)
class PtWhen(position: Position) : PtNode(position) {
val value: PtExpression
get() = children[0] as PtExpression
+3 -3
View File
@@ -13,11 +13,11 @@
</options>
<keywords keywords="&amp;;&amp;&lt;;&amp;&gt;;-&gt;;@;alias;and;as;asmsub;break;clobbers;continue;do;downto;else;extsub;false;for;goto;if;if_cc;if_cs;if_eq;if_mi;if_ne;if_neg;if_nz;if_pl;if_pos;if_vc;if_vs;if_z;in;inline;not;or;repeat;return;step;sub;to;true;unroll;until;when;while;xor;~" ignore_case="false" />
<keywords2 keywords="%address;%align;%asm;%asmbinary;%asminclude;%breakpoint;%encoding;%import;%ir;%jmptable;%launcher;%memtop;%option;%output;%zeropage;%zpallowed;%zpreserved;@align64;@alignpage;@alignword;@bank;@dirty;@nosplit;@nozp;@requirezp;@shared;@split;@zp;atascii:;c64os:;cp437:;default:;iso16:;iso5:;iso:;kata:;petscii:;sc:" />
<keywords3 keywords="bool;byte;const;float;long;str;ubyte;uword;void;word" />
<keywords3 keywords="^bool;^byte;^float;^str;^ubyte;^uword;^word;bool;byte;const;float;long;str;struct;ubyte;uword;void;word" />
<keywords4 keywords="abs;bmx;call;callfar;callfar2;cbm;clamp;cmp;conv;cx16;defer;diskio;divmod;floats;len;lsb;lsw;math;max;memory;min;mkword;msb;msw;peek;peekf;peekw;poke;pokef;pokew;psg;rol;rol2;ror;ror2;rrestore;rrestorex;rsave;rsavex;setlsb;setmsb;sgn;sizeof;sqrt;strings;sys;txt;verafx" />
</highlighting>
<extensionMap>
<mapping ext="prog8" />
<mapping ext="p8" />
<mapping ext="prog8" />
</extensionMap>
</filetype>
</filetype>
+1 -1
View File
@@ -24,7 +24,7 @@
<Keywords name="Folders in comment, open"></Keywords>
<Keywords name="Folders in comment, middle"></Keywords>
<Keywords name="Folders in comment, close"></Keywords>
<Keywords name="Keywords1">void const&#x000D;&#x000A;str&#x000D;&#x000A;byte ubyte bool&#x000D;&#x000A;long word uword&#x000D;&#x000A;float&#x000D;&#x000A;zp shared split nosplit requirezp nozp</Keywords>
<Keywords name="Keywords1">void const&#x000D;&#x000A;str&#x000D;&#x000A;byte ubyte bool&#x000D;&#x000A;long word uword&#x000D;&#x000A;float&#x000D;&#x000A;zp shared split nosplit requirezp nozp struct</Keywords>
<Keywords name="Keywords2">%address&#x000D;&#x000A;%asm&#x000D;&#x000A;%ir&#x000D;&#x000A;%asmbinary&#x000D;&#x000A;%asminclude&#x000D;&#x000A;%align&#x000D;&#x000A;%breakpoint&#x000D;&#x000A;%encoding&#x000D;&#x000A;%import&#x000D;&#x000A;%jmptable&#x000D;&#x000A;%memtop&#x000D;&#x000A;%launcher&#x000D;&#x000A;%option&#x000D;&#x000A;%output&#x000D;&#x000A;%zeropage&#x000D;&#x000A;%zpreserved&#x000D;&#x000A;%zpallowed</Keywords>
<Keywords name="Keywords3">inline sub asmsub extsub&#x000D;&#x000A;clobbers&#x000D;&#x000A;asm&#x000D;&#x000A;if&#x000D;&#x000A;when else&#x000D;&#x000A;if_cc if_cs if_eq if_mi if_neg if_nz if_pl if_pos if_vc if_vs if_z&#x000D;&#x000A;for in step do while repeat unroll&#x000D;&#x000A;break continue return goto</Keywords>
<Keywords name="Keywords4">alias abs call callfar callfar2 clamp cmp defer divmod len lsb lsw lsl lsr memory mkword min max msb msw peek peekw peekf poke pokew pokef rsave rsavex rrestore rrestorex rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof sqrtw</Keywords>
+1 -1
View File
@@ -24,7 +24,7 @@
<Keywords name="Folders in comment, open"></Keywords>
<Keywords name="Folders in comment, middle"></Keywords>
<Keywords name="Folders in comment, close"></Keywords>
<Keywords name="Keywords1">void const&#x000D;&#x000A;str&#x000D;&#x000A;byte ubyte bool&#x000D;&#x000A;long word uword&#x000D;&#x000A;float&#x000D;&#x000A;zp shared split nosplit requirezp nozp</Keywords>
<Keywords name="Keywords1">void const&#x000D;&#x000A;str&#x000D;&#x000A;byte ubyte bool&#x000D;&#x000A;long word uword&#x000D;&#x000A;float&#x000D;&#x000A;zp shared split nosplit requirezp nozp struct</Keywords>
<Keywords name="Keywords2">%address&#x000D;&#x000A;%asm&#x000D;&#x000A;%ir&#x000D;&#x000A;%asmbinary&#x000D;&#x000A;%asminclude&#x000D;&#x000A;%align&#x000D;&#x000A;%breakpoint&#x000D;&#x000A;%encoding&#x000D;&#x000A;%import&#x000D;&#x000A;%jmptable&#x000D;&#x000A;%memtop&#x000D;&#x000A;%launcher&#x000D;&#x000A;%option&#x000D;&#x000A;%output&#x000D;&#x000A;%zeropage&#x000D;&#x000A;%zpreserved&#x000D;&#x000A;%zpallowed</Keywords>
<Keywords name="Keywords3">inline sub asmsub extsub&#x000D;&#x000A;clobbers&#x000D;&#x000A;asm&#x000D;&#x000A;if&#x000D;&#x000A;when else&#x000D;&#x000A;if_cc if_cs if_eq if_mi if_neg if_nz if_pl if_pos if_vc if_vs if_z&#x000D;&#x000A;for in step do while repeat unroll&#x000D;&#x000A;break continue return goto</Keywords>
<Keywords name="Keywords4">alias abs call callfar callfar2 clamp cmp defer divmod len lsb lsw lsl lsr memory mkword min max msb msw peek peekw peekf poke pokew pokef rsave rsavex rrestore rrestorex rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof sqrtw</Keywords>
@@ -149,7 +149,7 @@ contexts:
- match: (\b\w+\.)
scope: entity.name.namespace.prog8
storage:
- match: (\b(ubyte|byte|word|uword|long|float|str)\b)
- match: (\b(ubyte|byte|word|uword|long|float|str|struct)\b)
scope: storage.type.prog8
- match: (\b(const)\b)
scope: storage.modifier.prog8
+11 -7
View File
@@ -198,10 +198,14 @@ class VmProgramLoader {
// zero out uninitialized ('bss') variables.
if(variable.uninitialized) {
if(variable.dt.isArray) {
val dt = variable.dt
val dt = variable.dt
if(dt.isArray) {
repeat(variable.length!!) {
when {
dt.isPointerArray -> {
memory.setUW(addr, 0u) // array of pointers is just array of word addresses
addr += 2
}
dt.isString || dt.isBoolArray || dt.isByteArray -> {
memory.setUB(addr, 0u)
addr++
@@ -225,11 +229,11 @@ class VmProgramLoader {
}
} else {
when {
variable.dt.isUnsignedByte || variable.dt.isBool -> memory.setUB(addr, 0u)
variable.dt.isSignedByte -> memory.setSB(addr, 0)
variable.dt.isUnsignedWord -> memory.setUW(addr, 0u)
variable.dt.isSignedWord -> memory.setSW(addr, 0)
variable.dt.isFloat -> memory.setFloat(addr, 0.0)
dt.isUnsignedByte || dt.isBool -> memory.setUB(addr, 0u)
dt.isSignedByte -> memory.setSB(addr, 0)
dt.isUnsignedWord || dt.isPointer -> memory.setUW(addr, 0u)
dt.isSignedWord -> memory.setSW(addr, 0)
dt.isFloat -> memory.setFloat(addr, 0.0)
else -> throw IRParseException("invalid dt")
}
}
@@ -1,5 +1,6 @@
package prog8.vm
import prog8.code.core.DataType
import prog8.code.core.IMemSizer
import prog8.code.core.IStringEncoding
import prog8.code.core.InternalCompilerException
@@ -19,6 +20,7 @@ internal class VmVariableAllocator(st: IRSymbolTable, val encoding: IStringEncod
for (variable in st.allVariables()) {
val memsize =
when {
variable.dt.isPointer -> memsizer.memorySize(DataType.UWORD, null) // a pointer is just a word address
variable.dt.isString -> variable.onetimeInitializationStringValue!!.first.length + 1 // include the zero byte
variable.dt.isNumericOrBool -> memsizer.memorySize(variable.dt, null)
variable.dt.isArray -> memsizer.memorySize(variable.dt, variable.length!!)