mirror of https://github.com/irmen/prog8.git
322 lines
13 KiB
Kotlin
322 lines
13 KiB
Kotlin
package prog8.intermediate
|
|
|
|
import prog8.code.Either
|
|
import prog8.code.core.*
|
|
import prog8.code.left
|
|
import prog8.code.right
|
|
|
|
|
|
fun DataType.typeString(length: Int?): String {
|
|
val lengthStr = if(length==0) "" else length.toString()
|
|
return when (this) {
|
|
DataType.BOOL -> "ubyte" // in IR , a boolean is represented by an ubyte.
|
|
DataType.UBYTE -> "ubyte"
|
|
DataType.BYTE -> "byte"
|
|
DataType.UWORD -> "uword"
|
|
DataType.WORD -> "word"
|
|
DataType.LONG -> "long"
|
|
DataType.FLOAT -> "float"
|
|
DataType.STR -> "ubyte[$lengthStr]" // here string doesn't exist as a seperate datatype anymore
|
|
DataType.ARRAY_BOOL -> "ubyte[$lengthStr]" // in IR , a boolean is represented by an ubyte.
|
|
DataType.ARRAY_UB -> "ubyte[$lengthStr]"
|
|
DataType.ARRAY_B -> "byte[$lengthStr]"
|
|
DataType.ARRAY_UW -> "uword[$lengthStr]"
|
|
DataType.ARRAY_W -> "word[$lengthStr]"
|
|
DataType.ARRAY_F -> "float[$lengthStr]"
|
|
DataType.ARRAY_UW_SPLIT -> "@split uword[$lengthStr]" // should be 2 separate byte arrays by now really
|
|
DataType.ARRAY_W_SPLIT -> "@split word[$lengthStr]" // should be 2 separate byte arrays by now really
|
|
DataType.UNDEFINED -> throw IllegalArgumentException("wrong dt")
|
|
}
|
|
}
|
|
|
|
fun convertIRType(typestr: String): IRDataType? {
|
|
return when(typestr.lowercase()) {
|
|
"" -> null
|
|
".b" -> IRDataType.BYTE
|
|
".w" -> IRDataType.WORD
|
|
".f" -> IRDataType.FLOAT
|
|
else -> throw IRParseException("invalid type $typestr")
|
|
}
|
|
}
|
|
|
|
fun parseIRValue(value: String): Double {
|
|
return if(value.startsWith("-"))
|
|
-parseIRValue(value.substring(1))
|
|
else if(value.startsWith('$'))
|
|
value.substring(1).toInt(16).toDouble()
|
|
else if(value.startsWith('%'))
|
|
value.substring(1).toInt(2).toDouble()
|
|
else if(value.startsWith("0x"))
|
|
value.substring(2).toInt(16).toDouble()
|
|
else if(value.startsWith('_'))
|
|
throw IRParseException("attempt to parse a label as numeric value")
|
|
else if(value.startsWith('&'))
|
|
throw IRParseException("address-of should be done with normal LOAD <symbol>")
|
|
else if(value.startsWith('@'))
|
|
throw IRParseException("address-of @ should have been handled earlier")
|
|
else
|
|
return value.toDouble()
|
|
}
|
|
|
|
|
|
private val instructionPattern = Regex("""([a-z]+)(\.b|\.w|\.f)?(.*)""", RegexOption.IGNORE_CASE)
|
|
private val labelPattern = Regex("""_([a-zA-Z\d\._]+):""")
|
|
|
|
fun parseIRCodeLine(line: String): Either<IRInstruction, String> {
|
|
val labelmatch = labelPattern.matchEntire(line.trim())
|
|
if(labelmatch!=null)
|
|
return right(labelmatch.groupValues[1]) // it's a label.
|
|
|
|
val match = instructionPattern.matchEntire(line)
|
|
?: throw IRParseException("invalid IR instruction: $line")
|
|
val (instr, typestr, rest) = match.destructured
|
|
val opcode = try {
|
|
Opcode.valueOf(instr.uppercase())
|
|
} catch (ax: IllegalArgumentException) {
|
|
throw IRParseException("invalid vmasm instruction: $instr")
|
|
}
|
|
var type: IRDataType? = convertIRType(typestr)
|
|
val formats = instructionFormats.getValue(opcode)
|
|
val format: InstructionFormat =
|
|
if(type !in formats) {
|
|
type = IRDataType.BYTE
|
|
if(type !in formats)
|
|
formats.getValue(null)
|
|
else
|
|
formats.getValue(type)
|
|
} else {
|
|
formats.getValue(type)
|
|
}
|
|
|
|
// parse the operands
|
|
val operands = if(rest.isBlank()) emptyList() else rest.split(",").map{ it.trim() }.toMutableList()
|
|
var reg1: Int? = null
|
|
var reg2: Int? = null
|
|
var reg3: Int? = null
|
|
var fpReg1: Int? = null
|
|
var fpReg2: Int? = null
|
|
var immediateInt: Int? = null
|
|
var immediateFp: Double? = null
|
|
var address: Int? = null
|
|
var labelSymbol: String? = null
|
|
|
|
fun parseValueOrPlaceholder(operand: String): Double? {
|
|
return if(operand[0].isLetter()) {
|
|
null
|
|
} else {
|
|
parseIRValue(operand)
|
|
}
|
|
}
|
|
if(format.sysCall) {
|
|
val call = parseCall(rest)
|
|
val syscallNum = call.address ?: parseIRValue(call.target ?: "").toInt()
|
|
return left(IRInstruction(Opcode.SYSCALL, immediate = syscallNum, fcallArgs = FunctionCallArgs(call.args, call.returns)))
|
|
} else if (format.funcCall) {
|
|
val call = parseCall(rest)
|
|
return left(IRInstruction(Opcode.CALL, address = call.address, labelSymbol = call.target, fcallArgs = FunctionCallArgs(call.args, call.returns)))
|
|
} else {
|
|
operands.forEach { oper ->
|
|
if (oper[0] == '&')
|
|
throw IRParseException("address-of should be done with normal LOAD <symbol>")
|
|
else if (oper[0] in "rR") {
|
|
if (reg1 == null) reg1 = oper.substring(1).toInt()
|
|
else if (reg2 == null) reg2 = oper.substring(1).toInt()
|
|
else if (reg3 == null) reg3 = oper.substring(1).toInt()
|
|
else throw IRParseException("too many register operands")
|
|
} else if (oper[0] in "fF" && oper[1] in "rR") {
|
|
if (fpReg1 == null) fpReg1 = oper.substring(2).toInt()
|
|
else if (fpReg2 == null) fpReg2 = oper.substring(2).toInt()
|
|
else throw IRParseException("too many fp register operands")
|
|
} else if (oper[0].isDigit() || oper[0] == '$' || oper[0] == '%' || oper[0] == '-' || oper.startsWith("0x")) {
|
|
val value = parseIRValue(oper)
|
|
if (format.immediate) {
|
|
if (immediateInt == null && immediateFp == null) {
|
|
if (type == IRDataType.FLOAT)
|
|
immediateFp = value
|
|
else
|
|
immediateInt = value.toInt()
|
|
} else {
|
|
address = value.toInt()
|
|
}
|
|
} else {
|
|
address = value.toInt()
|
|
}
|
|
} else {
|
|
if (!oper[0].isLetter())
|
|
throw IRParseException("expected symbol name: $oper")
|
|
labelSymbol = oper
|
|
val value = parseValueOrPlaceholder(oper)
|
|
if (value != null)
|
|
address = value.toInt()
|
|
}
|
|
}
|
|
}
|
|
|
|
if(type!=null && type !in formats)
|
|
throw IRParseException("invalid type code for $line")
|
|
if(format.reg1!=OperandDirection.UNUSED && reg1==null)
|
|
throw IRParseException("needs reg1 for $line")
|
|
if(format.reg2!=OperandDirection.UNUSED && reg2==null)
|
|
throw IRParseException("needs reg2 for $line")
|
|
if(format.reg3!=OperandDirection.UNUSED && reg3==null)
|
|
throw IRParseException("needs reg3 for $line")
|
|
if(format.fpReg1!=OperandDirection.UNUSED && fpReg1==null)
|
|
throw IRParseException("needs fpReg1 for $line")
|
|
if(format.fpReg2!=OperandDirection.UNUSED && fpReg2==null)
|
|
throw IRParseException("needs fpReg2 for $line")
|
|
if(format.address!=OperandDirection.UNUSED && address==null && labelSymbol==null)
|
|
throw IRParseException("needs address or symbol for $line")
|
|
if(format.reg1==OperandDirection.UNUSED && reg1!=null)
|
|
throw IRParseException("invalid reg1 for $line")
|
|
if(format.reg2==OperandDirection.UNUSED && reg2!=null)
|
|
throw IRParseException("invalid reg2 for $line")
|
|
if(format.reg3==OperandDirection.UNUSED && reg3!=null)
|
|
throw IRParseException("invalid reg3 for $line")
|
|
if(format.fpReg1==OperandDirection.UNUSED && fpReg1!=null)
|
|
throw IRParseException("invalid fpReg1 for $line")
|
|
if(format.fpReg2==OperandDirection.UNUSED && fpReg2!=null)
|
|
throw IRParseException("invalid fpReg2 for $line")
|
|
if(format.immediate && opcode!=Opcode.SYSCALL) {
|
|
if(immediateInt==null && immediateFp==null && labelSymbol==null)
|
|
throw IRParseException("needs value or symbol for $line")
|
|
when (type) {
|
|
IRDataType.BYTE -> {
|
|
if (immediateInt!=null && (immediateInt!! < -128 || immediateInt!! > 255))
|
|
throw IRParseException("immediate value out of range for byte: $immediateInt")
|
|
}
|
|
IRDataType.WORD -> {
|
|
if (immediateInt!=null && (immediateInt!! < -32768 || immediateInt!! > 65535))
|
|
throw IRParseException("immediate value out of range for word: $immediateInt")
|
|
}
|
|
IRDataType.FLOAT -> {}
|
|
null -> {}
|
|
}
|
|
}
|
|
|
|
if(format.address!=OperandDirection.UNUSED && address==null && labelSymbol==null)
|
|
throw IRParseException("requires address or symbol for $line")
|
|
|
|
var offset: Int? = null
|
|
if(labelSymbol!=null) {
|
|
if (labelSymbol!![0] == 'r' && labelSymbol!![1].isDigit())
|
|
throw IRParseException("labelsymbol confused with register?: $labelSymbol")
|
|
if('+' in labelSymbol!!) {
|
|
val offsetStr = labelSymbol!!.substringAfterLast('+')
|
|
if (offsetStr.isNotEmpty()) {
|
|
offset = offsetStr.toInt()
|
|
labelSymbol = labelSymbol!!.substringBeforeLast('+')
|
|
}
|
|
}
|
|
}
|
|
|
|
return left(IRInstruction(opcode, type, reg1, reg2, reg3, fpReg1, fpReg2, immediateInt, immediateFp, address, labelSymbol = labelSymbol, symbolOffset = offset))
|
|
}
|
|
|
|
private class ParsedCall(
|
|
val target: String?,
|
|
val address: Int?,
|
|
val args: List<FunctionCallArgs.ArgumentSpec>,
|
|
val returns: List<FunctionCallArgs.RegSpec>
|
|
)
|
|
|
|
private fun parseCall(rest: String): ParsedCall {
|
|
|
|
fun parseRegspec(reg: String): FunctionCallArgs.RegSpec {
|
|
val pattern = Regex("f?r([0-9]+)\\.(.)(@.{1,2})?$")
|
|
val match = pattern.matchEntire(reg) ?: throw IRParseException("invalid regspec $reg")
|
|
val num = match.groups[1]!!.value.toInt()
|
|
val type = when(match.groups[2]!!.value) {
|
|
"b" -> IRDataType.BYTE
|
|
"w" -> IRDataType.WORD
|
|
"f" -> IRDataType.FLOAT
|
|
else -> throw IRParseException("invalid type spec in $reg")
|
|
}
|
|
val cpuRegister: RegisterOrStatusflag? =
|
|
if(match.groups[3]!=null) {
|
|
val cpuRegStr = match.groups[3]!!.value.drop(1)
|
|
parseRegisterOrStatusflag(cpuRegStr)
|
|
} else null
|
|
return FunctionCallArgs.RegSpec(type, num, cpuRegister)
|
|
}
|
|
|
|
fun parseReturnRegspec(regs: String?): List<FunctionCallArgs.RegSpec> {
|
|
if(regs==null)
|
|
return emptyList()
|
|
return regs.split(',').map { reg->
|
|
if (reg.startsWith('@')) {
|
|
FunctionCallArgs.RegSpec(IRDataType.BYTE, -1, parseRegisterOrStatusflag(reg.drop(1)))
|
|
} else {
|
|
parseRegspec(reg)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun parseArgs(args: String): List<FunctionCallArgs.ArgumentSpec> {
|
|
if(args.isBlank())
|
|
return emptyList()
|
|
return args.split(',').map {
|
|
if(it.contains('=')) {
|
|
val (argVar, argReg) = it.split('=')
|
|
FunctionCallArgs.ArgumentSpec(argVar, null, parseRegspec(argReg)) // address will be set later
|
|
} else {
|
|
FunctionCallArgs.ArgumentSpec("", null, parseRegspec(it)) // address will be set later
|
|
}
|
|
}
|
|
}
|
|
|
|
val pattern = Regex("(?<target>.+?)\\((?<arglist>.*?)\\)(:(?<returns>.+?))?")
|
|
val match = pattern.matchEntire(rest.replace(" ","")) ?: throw IRParseException("invalid call spec $rest")
|
|
val target = match.groups["target"]!!.value
|
|
val args = match.groups["arglist"]!!.value
|
|
val arguments = parseArgs(args)
|
|
val returns = match.groups["returns"]?.value
|
|
var address: Int? = null
|
|
var actualTarget: String? = target
|
|
|
|
if(target.startsWith('$') || target[0].isDigit()) {
|
|
address = parseIRValue(target).toInt()
|
|
actualTarget = null
|
|
}
|
|
|
|
return ParsedCall(
|
|
actualTarget,
|
|
address,
|
|
arguments,
|
|
parseReturnRegspec(returns)
|
|
)
|
|
}
|
|
|
|
|
|
internal fun parseRegisterOrStatusflag(sourceregs: String): RegisterOrStatusflag {
|
|
var reg: RegisterOrPair? = null
|
|
var sf: Statusflag? = null
|
|
|
|
val regs = if(sourceregs.endsWith(".b") || sourceregs.endsWith(".w") || sourceregs.endsWith(".f"))
|
|
sourceregs.dropLast(2) else sourceregs
|
|
|
|
try {
|
|
reg = RegisterOrPair.valueOf(regs)
|
|
} catch (x: IllegalArgumentException) {
|
|
try {
|
|
sf = Statusflag.valueOf(regs)
|
|
} catch(x: IllegalArgumentException) {
|
|
throw IRParseException("invalid IR register or statusflag: $regs")
|
|
}
|
|
}
|
|
return RegisterOrStatusflag(reg, sf)
|
|
}
|
|
|
|
|
|
fun irType(type: DataType): IRDataType {
|
|
return when(type) {
|
|
DataType.BOOL,
|
|
DataType.UBYTE,
|
|
DataType.BYTE -> IRDataType.BYTE
|
|
DataType.UWORD,
|
|
DataType.WORD -> IRDataType.WORD
|
|
DataType.FLOAT -> IRDataType.FLOAT
|
|
in PassByReferenceDatatypes -> IRDataType.WORD
|
|
else -> throw AssemblyError("no IR datatype for $type")
|
|
}
|
|
}
|