IRFileReader parses the p8ir file with xml parser

This commit is contained in:
Irmen de Jong 2022-11-12 16:44:42 +01:00
parent e6688f4b9d
commit 267b6f49b5
5 changed files with 415 additions and 405 deletions

View File

@ -291,12 +291,7 @@ class IRCodeGen(
}
is PtConditionalBranch -> translate(node)
is PtInlineAssembly -> listOf(IRInlineAsmChunk(null, node.assembly, node.isIR, null))
is PtIncludeBinary -> {
val data = node.file.readBytes()
.drop(node.offset?.toInt() ?: 0)
.take(node.length?.toInt() ?: Int.MAX_VALUE)
listOf(IRInlineBinaryChunk(null, data.map { it.toUByte() }, null))
}
is PtIncludeBinary -> listOf(IRInlineBinaryChunk(null, readBinaryData(node), null))
is PtAddressOf,
is PtContainmentCheck,
is PtMemoryByte,
@ -327,6 +322,13 @@ class IRCodeGen(
return chunks
}
private fun readBinaryData(node: PtIncludeBinary): Collection<UByte> {
return node.file.readBytes()
.drop(node.offset?.toInt() ?: 0)
.take(node.length?.toInt() ?: Int.MAX_VALUE)
.map { it.toUByte() }
}
private fun translate(branch: PtConditionalBranch): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val elseLabel = createLabelName()
@ -1106,8 +1108,8 @@ class IRCodeGen(
child.name,
child.address,
child.clobbers,
child.parameters.map { Pair(it.first.type, it.second) }, // note: the name of the asmsub param is not used anymore.
child.returnTypes.zip(child.retvalRegisters),
child.parameters.map { IRAsmSubroutine.IRAsmParam(it.second, it.first.type) }, // note: the name of the asmsub param is not used anymore.
child.returnTypes.zip(child.retvalRegisters).map { IRAsmSubroutine.IRAsmParam(it.second, it.first) },
asmChunk,
child.position
)
@ -1116,6 +1118,9 @@ class IRCodeGen(
is PtInlineAssembly -> {
irBlock += IRInlineAsmChunk(null, child.assembly, child.isIR, null)
}
is PtIncludeBinary -> {
irBlock += IRInlineBinaryChunk(null, readBinaryData(child), null)
}
else -> TODO("weird child node $child")
}
}

View File

@ -3,7 +3,7 @@ TODO
For next release
^^^^^^^^^^^^^^^^
- IRFileReader should parse the p8ir file with xml parser
- IRBlock should be able to contain inline binary data (with optional label etc)
- 6502 codegen: make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as ``p8v_``? Or not worth it (most 3 letter opcodes as variables are nonsensical anyway)
then we can get rid of the instruction lists in the machinedefinitions as well. This is already no problem at all in the IR codegen.
- create BSS section in output program and put StStaticVariables in there with bss=true. Don't forget to add init code to zero out everything that was put in bss. If array in bss->only zero ONCE! So requires self-modifying code

View File

@ -3,51 +3,52 @@ package prog8.intermediate
import prog8.code.*
import prog8.code.core.*
import prog8.code.target.*
import java.io.StringReader
import java.nio.file.Path
import javax.xml.stream.XMLEventReader
import javax.xml.stream.XMLInputFactory
import kotlin.io.path.Path
import kotlin.io.path.bufferedReader
import kotlin.io.path.inputStream
class IRFileReader {
fun read(irSourceCode: String): IRProgram {
// TODO parse the source via XML parser
// val isrc = InputSource(StringReader(irSourceCode))
// val xmlDoc: Document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(isrc)
// xmlDoc.normalizeDocument()
// println("ROOT NODE: ${xmlDoc.documentElement.nodeName}") // TODO
return parseProgram(irSourceCode.lineSequence().iterator())
StringReader(irSourceCode).use { stream ->
val reader = XMLInputFactory.newInstance().createXMLEventReader(stream)
try {
return parseProgram(reader)
} finally {
reader.close()
}
}
}
fun read(irSourceFile: Path): IRProgram {
println("Reading intermediate representation from $irSourceFile")
// TODO parse the source via XML parser
// val xmlDoc: Document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(irSourceFile.toFile())
// xmlDoc.normalizeDocument()
// println("ROOT NODE: ${xmlDoc.documentElement.nodeName}") // TODO
irSourceFile.bufferedReader(charset=Charsets.UTF_8).use { reader ->
return parseProgram(reader.lineSequence().iterator())
irSourceFile.inputStream().use { stream ->
val reader = XMLInputFactory.newInstance().createXMLEventReader(stream)
try {
return parseProgram(reader)
} finally {
reader.close()
}
}
}
private fun parseProgram(lines: Iterator<String>): IRProgram {
val xmlheader = lines.next()
if(!xmlheader.startsWith("<?xml version=\"1.0\""))
throw IRParseException("missing xml header")
val programPattern = Regex("<PROGRAM NAME=\"(.+)\">")
val line = lines.next()
val match = programPattern.matchEntire(line) ?: throw IRParseException("invalid PROGRAM")
val programName = match.groups[1]!!.value
val options = parseOptions(lines)
val asmsymbols = parseAsmSymbols(lines)
val variables = parseVariables(lines, options.dontReinitGlobals)
val memorymapped = parseMemMapped(lines)
val slabs = parseSlabs(lines)
val initGlobals = parseInitGlobals(lines)
val blocks = parseBlocksUntilProgramEnd(lines)
private fun parseProgram(reader: XMLEventReader): IRProgram {
require(reader.nextEvent().isStartDocument)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="PROGRAM") { "missing PROGRAM" }
val programName = start.attributes.asSequence().single { it.name.localPart == "NAME" }.value
val options = parseOptions(reader)
val asmsymbols = parseAsmSymbols(reader)
val variables = parseVariables(reader, options.dontReinitGlobals)
val memorymapped = parseMemMapped(reader)
val slabs = parseSlabs(reader)
val initGlobals = parseInitGlobals(reader)
val blocks = parseBlocksUntilProgramEnd(reader)
val st = IRSymbolTable(null)
asmsymbols.forEach { (name, value) -> st.addAsmSymbol(name, value)}
@ -65,26 +66,13 @@ class IRFileReader {
return program
}
private fun parseAsmSymbols(lines: Iterator<String>): Map<String, String> {
val symbols = mutableMapOf<String, String>()
var line = lines.next()
while(line.isBlank())
line = lines.next()
if(line!="<ASMSYMBOLS>")
throw IRParseException("invalid ASMSYMBOLS")
while(true) {
line = lines.next()
if(line=="</ASMSYMBOLS>")
return symbols
val (name, value) = line.split('=')
symbols[name] = value
}
}
private fun parseOptions(reader: XMLEventReader): CompilationOptions {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="OPTIONS") { "missing OPTIONS" }
val text = readText(reader).trim()
require(reader.nextEvent().isEndElement)
private fun parseOptions(lines: Iterator<String>): CompilationOptions {
var line = lines.next()
while(line.isBlank())
line = lines.next()
var target: ICompilationTarget = VMTarget()
var outputType = OutputType.PRG
var launcher = CbmPrgLauncherType.NONE
@ -95,37 +83,35 @@ class IRFileReader {
var optimize = true
var evalStackBaseAddress: UInt? = null
var outputDir = Path("")
if(line!="<OPTIONS>")
throw IRParseException("invalid OPTIONS")
while(true) {
line = lines.next()
if(line=="</OPTIONS>")
break
val (name, value) = line.split('=', limit=2)
when(name) {
"compTarget" -> {
target = when(value) {
VMTarget.NAME -> VMTarget()
C64Target.NAME -> C64Target()
C128Target.NAME -> C128Target()
AtariTarget.NAME -> AtariTarget()
Cx16Target.NAME -> Cx16Target()
else -> throw IRParseException("invalid target $value")
if(text.isNotBlank()) {
text.lineSequence().forEach { line ->
val (name, value) = line.split('=', limit=2)
when(name) {
"compTarget" -> {
target = when(value) {
VMTarget.NAME -> VMTarget()
C64Target.NAME -> C64Target()
C128Target.NAME -> C128Target()
AtariTarget.NAME -> AtariTarget()
Cx16Target.NAME -> Cx16Target()
else -> throw IRParseException("invalid target $value")
}
}
"output" -> outputType = OutputType.valueOf(value)
"launcher" -> launcher = CbmPrgLauncherType.valueOf(value)
"zeropage" -> zeropage = ZeropageType.valueOf(value)
"loadAddress" -> loadAddress = value.toUInt()
"dontReinitGlobals" -> dontReinitGlobals = value.toBoolean()
"evalStackBaseAddress" -> evalStackBaseAddress = if(value=="null") null else parseIRValue(value).toUInt()
"zpReserved" -> {
val (zpstart, zpend) = value.split(',')
zpReserved.add(UIntRange(zpstart.toUInt(), zpend.toUInt()))
}
"outputDir" -> outputDir = Path(value)
"optimize" -> optimize = value.toBoolean()
else -> throw IRParseException("illegal OPTION $name")
}
"output" -> outputType = OutputType.valueOf(value)
"launcher" -> launcher = CbmPrgLauncherType.valueOf(value)
"zeropage" -> zeropage = ZeropageType.valueOf(value)
"loadAddress" -> loadAddress = value.toUInt()
"dontReinitGlobals" -> dontReinitGlobals = value.toBoolean()
"evalStackBaseAddress" -> evalStackBaseAddress = if(value=="null") null else parseIRValue(value).toUInt()
"zpReserved" -> {
val (start, end) = value.split(',')
zpReserved.add(UIntRange(start.toUInt(), end.toUInt()))
}
"outputDir" -> outputDir = Path(value)
"optimize" -> optimize = value.toBoolean()
else -> throw IRParseException("illegal OPTION $name")
}
}
@ -145,85 +131,317 @@ class IRFileReader {
)
}
private fun parseVariables(lines: Iterator<String>, dontReinitGlobals: Boolean): List<StStaticVariable> {
var line = lines.next()
while(line.isBlank())
line = lines.next()
if(line!="<VARIABLES>")
throw IRParseException("invalid VARIABLES")
val variables = mutableListOf<StStaticVariable>()
val varPattern = Regex("(.+?)(\\[.+?\\])? (.+)=(.*?) (zp=(.+))?")
while(true) {
line = lines.next()
if(line=="</VARIABLES>")
break
// examples:
// uword main.start.qq2=0 zp=REQUIRE_ZP
// ubyte[6] main.start.namestring=105,114,109,101,110,0
val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid VARIABLE $line")
val (type, arrayspec, name, value, _, zpwish) = match.destructured
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
val dt: DataType = parseDatatype(type, arraysize!=null)
val zp = if(zpwish.isBlank()) ZeropageWish.DONTCARE else ZeropageWish.valueOf(zpwish)
val bss: Boolean
var initNumeric: Double? = null
var initArray: StArray? = null
when(dt) {
in NumericDatatypes -> {
if(value.isBlank()) {
require(!dontReinitGlobals)
bss = true
} else {
require(dontReinitGlobals)
bss = false
initNumeric = parseIRValue(value).toDouble()
}
}
in ArrayDatatypes -> {
if(value.isBlank()) {
bss = true
initArray = emptyList()
} else {
bss = false
initArray = value.split(',').map {
if (it.startsWith('@'))
StArrayElement(null, it.drop(1).split('.'))
else
StArrayElement(parseIRValue(it).toDouble(), null)
}
}
}
DataType.STR -> throw IRParseException("STR should have been converted to byte array")
else -> throw IRParseException("weird dt")
}
private fun parseAsmSymbols(reader: XMLEventReader): Map<String, String> {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="ASMSYMBOLS") { "missing ASMSYMBOLS" }
val text = readText(reader).trim()
require(reader.nextEvent().isEndElement)
variables.add(StStaticVariable(name, dt, bss, initNumeric, null, initArray, arraysize, zp, Position.DUMMY))
}
return variables
return if(text.isBlank())
emptyMap()
else
text.lineSequence().associate {
val (name, value) = it.split('=')
name to value
}
}
private fun parseMemMapped(lines: Iterator<String>): List<StMemVar> {
var line = lines.next()
while(line.isBlank())
line = lines.next()
if(line!="<MEMORYMAPPEDVARIABLES>")
throw IRParseException("invalid MEMORYMAPPEDVARIABLES")
val memvars = mutableListOf<StMemVar>()
val mappedPattern = Regex("@(.+?)(\\[.+?\\])? (.+)=(.+)")
while(true) {
line = lines.next()
if(line=="</MEMORYMAPPEDVARIABLES>")
break
// examples:
// &uword main.start.mapped=49152
// &ubyte[20] main.start.mappedarray=49408
val match = mappedPattern.matchEntire(line) ?: throw IRParseException("invalid MEMORYMAPPEDVARIABLES $line")
val (type, arrayspec, name, address) = match.destructured
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
val dt: DataType = parseDatatype(type, arraysize!=null)
memvars.add(StMemVar(name, dt, parseIRValue(address).toUInt(), arraysize, Position.DUMMY))
private fun parseVariables(reader: XMLEventReader, dontReinitGlobals: Boolean): List<StStaticVariable> {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="VARIABLES") { "missing VARIABLES" }
val text = readText(reader).trim()
require(reader.nextEvent().isEndElement)
return if(text.isBlank())
emptyList()
else {
val varPattern = Regex("(.+?)(\\[.+?\\])? (.+)=(.*?) (zp=(.+))?")
val variables = mutableListOf<StStaticVariable>()
text.lineSequence().forEach { line ->
// examples:
// uword main.start.qq2=0 zp=REQUIRE_ZP
// ubyte[6] main.start.namestring=105,114,109,101,110,0
val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid VARIABLE $line")
val (type, arrayspec, name, value, _, zpwish) = match.destructured
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
val dt: DataType = parseDatatype(type, arraysize!=null)
val zp = if(zpwish.isBlank()) ZeropageWish.DONTCARE else ZeropageWish.valueOf(zpwish)
val bss: Boolean
var initNumeric: Double? = null
var initArray: StArray? = null
when(dt) {
in NumericDatatypes -> {
if(value.isBlank()) {
require(!dontReinitGlobals)
bss = true
} else {
require(dontReinitGlobals)
bss = false
initNumeric = parseIRValue(value).toDouble()
}
}
in ArrayDatatypes -> {
if(value.isBlank()) {
bss = true
initArray = emptyList()
} else {
bss = false
initArray = value.split(',').map {
if (it.startsWith('@'))
StArrayElement(null, it.drop(1).split('.'))
else
StArrayElement(parseIRValue(it).toDouble(), null)
}
}
}
DataType.STR -> throw IRParseException("STR should have been converted to byte array")
else -> throw IRParseException("weird dt")
}
variables.add(StStaticVariable(name, dt, bss, initNumeric, null, initArray, arraysize, zp, Position.DUMMY))
}
return variables
}
return memvars
}
private fun parseMemMapped(reader: XMLEventReader): List<StMemVar> {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="MEMORYMAPPEDVARIABLES") { "missing MEMORYMAPPEDVARIABLES" }
val text = readText(reader).trim()
require(reader.nextEvent().isEndElement)
return if(text.isBlank())
emptyList()
else {
val memvars = mutableListOf<StMemVar>()
val mappedPattern = Regex("@(.+?)(\\[.+?\\])? (.+)=(.+)")
text.lineSequence().forEach { line ->
// examples:
// @uword main.start.mapped=49152
// @ubyte[20] main.start.mappedarray=49408
val match = mappedPattern.matchEntire(line) ?: throw IRParseException("invalid MEMORYMAPPEDVARIABLES $line")
val (type, arrayspec, name, address) = match.destructured
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
val dt: DataType = parseDatatype(type, arraysize!=null)
memvars.add(StMemVar(name, dt, parseIRValue(address).toUInt(), arraysize, Position.DUMMY))
}
memvars
}
}
private fun parseSlabs(reader: XMLEventReader): List<StMemorySlab> {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="MEMORYSLABS") { "missing MEMORYSLABS" }
val text = readText(reader).trim()
require(reader.nextEvent().isEndElement)
return if(text.isBlank())
emptyList()
else {
val slabs = mutableListOf<StMemorySlab>()
val slabPattern = Regex("SLAB (.+) (.+) (.+)")
text.lineSequence().forEach { line ->
// example: "SLAB slabname 4096 0"
val match = slabPattern.matchEntire(line) ?: throw IRParseException("invalid SLAB $line")
val (name, size, align) = match.destructured
slabs.add(StMemorySlab(name, size.toUInt(), align.toUInt(), Position.DUMMY))
}
slabs
}
}
private fun parseInitGlobals(reader: XMLEventReader): IRCodeChunk {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="INITGLOBALS") { "missing INITGLOBALS" }
skipText(reader)
val chunk: IRCodeChunk = if(reader.peek().isStartElement)
parseCodeChunk(reader)
else
IRCodeChunk(null, null)
skipText(reader)
require(reader.nextEvent().isEndElement)
return chunk
}
private fun parseCodeChunk(reader: XMLEventReader): IRCodeChunk {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="C") { "missing C" }
val label = start.attributes.asSequence().singleOrNull { it.name.localPart == "LABEL" }?.value?.ifBlank { null }
val text = readText(reader).trim()
val chunk = IRCodeChunk(label, null)
if(text.isNotBlank()) {
text.lineSequence().forEach { line ->
if (line.isNotBlank() && !line.startsWith(';')) {
val result = parseIRCodeLine(line, null, mutableMapOf())
result.fold(
ifLeft = {
chunk += it
},
ifRight = {
throw IRParseException("code chunk should not contain a separate label line anymore, this should be the proper label of a new separate chunk")
}
)
}
}
}
require(reader.nextEvent().isEndElement)
return chunk
}
private fun parseBlocksUntilProgramEnd(reader: XMLEventReader): List<IRBlock> {
val blocks = mutableListOf<IRBlock>()
skipText(reader)
while(reader.peek().isStartElement) {
blocks.add(parseBlock(reader))
skipText(reader)
}
return blocks
}
private fun parseBlock(reader: XMLEventReader): IRBlock {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="BLOCK") { "missing BLOCK" }
val attrs = start.attributes.asSequence().associate { it.name.localPart to it.value }
val block = IRBlock(
attrs.getValue("NAME"),
if(attrs.getValue("ADDRESS")=="null") null else parseIRValue(attrs.getValue("ADDRESS")).toUInt(),
IRBlock.BlockAlignment.valueOf(attrs.getValue("ALIGN")),
parsePosition(attrs.getValue("POS")))
skipText(reader)
while(reader.peek().isStartElement) {
when(reader.peek().asStartElement().name.localPart) {
"SUB" -> block += parseSubroutine(reader)
"ASMSUB" -> block += parseAsmSubroutine(reader)
"INLINEASM" -> block += parseInlineAssembly(reader)
else -> throw IRParseException("invalid line in BLOCK: ${reader.peek()}")
}
skipText(reader)
}
require(reader.nextEvent().isEndElement)
return block
}
private fun parseSubroutine(reader: XMLEventReader): IRSubroutine {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="SUB") { "missing SUB" }
val attrs = start.attributes.asSequence().associate { it.name.localPart to it.value }
val returntype = attrs.getValue("RETURNTYPE")
skipText(reader)
val sub = IRSubroutine(attrs.getValue("NAME"),
parseParameters(reader),
if(returntype=="null") null else parseDatatype(returntype, false),
parsePosition(attrs.getValue("POS")))
skipText(reader)
while(reader.peek().isStartElement) {
when(reader.peek().asStartElement().name.localPart) {
"C" -> sub += parseCodeChunk(reader)
"BYTES" -> sub += parseBinaryBytes(reader)
"INLINEASM" -> sub += parseInlineAssembly(reader)
else -> throw IRParseException("invalid line in SUB: ${reader.peek()}")
}
skipText(reader)
}
require(reader.nextEvent().isEndElement)
return sub
}
private fun parseParameters(reader: XMLEventReader): List<IRSubroutine.IRParam> {
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="PARAMS") { "missing PARAMS" }
val text = readText(reader).trim()
require(reader.nextEvent().isEndElement)
return if(text.isBlank())
emptyList()
else {
text.lines().map { line ->
val (datatype, name) = line.split(' ')
val dt = parseDatatype(datatype, datatype.contains('['))
// val orig = variables.single { it.dt==dt && it.name==name}
IRSubroutine.IRParam(name, dt)
}
}
}
private fun parseBinaryBytes(reader: XMLEventReader): IRInlineBinaryChunk {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="BYTES") { "missing BYTES" }
val label = start.attributes.asSequence().singleOrNull { it.name.localPart == "LABEL" }?.value?.ifBlank { null }
val text = readText(reader).replace("\n", "")
require(reader.nextEvent().isEndElement)
val bytes = text.windowed(2, step = 2).map { it.toUByte(16) }
return IRInlineBinaryChunk(label, bytes, null)
}
private fun parseAsmSubroutine(reader: XMLEventReader): IRAsmSubroutine {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="ASMSUB") { "missing ASMSUB" }
val attrs = start.attributes.asSequence().associate { it.name.localPart to it.value }
val params = parseAsmParameters(reader)
val assembly = parseInlineAssembly(reader)
skipText(reader)
require(reader.nextEvent().isEndElement)
val clobbers = attrs.getValue("CLOBBERS")
val clobberRegs = if(clobbers.isBlank()) emptyList() else clobbers.split(',').map { CpuRegister.valueOf(it) }
val returns = attrs.getValue("RETURNS").split(',').map { rs ->
val (regstr, dtstr) = rs.split(':')
val dt = parseDatatype(dtstr, false)
val regsf = parseRegisterOrStatusflag(regstr)
IRAsmSubroutine.IRAsmParam(regsf, dt)
}
return IRAsmSubroutine(
attrs.getValue("NAME"),
if(attrs.getValue("ADDRESS")=="null") null else parseIRValue(attrs.getValue("ADDRESS")).toUInt(),
clobberRegs.toSet(),
params,
returns,
assembly,
parsePosition(attrs.getValue("POS"))
)
}
private fun parseAsmParameters(reader: XMLEventReader): List<IRAsmSubroutine.IRAsmParam> {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="ASMPARAMS") { "missing ASMPARAMS" }
val text = readText(reader).trim()
require(reader.nextEvent().isEndElement)
return if(text.isBlank())
emptyList()
else {
text.lines().map { line ->
val (datatype, regOrSf) = line.split(' ')
val dt = parseDatatype(datatype, datatype.contains('['))
val regsf = parseRegisterOrStatusflag(regOrSf)
IRAsmSubroutine.IRAsmParam(regsf, dt)
}
}
}
private fun parseInlineAssembly(reader: XMLEventReader): IRInlineAsmChunk {
skipText(reader)
val start = reader.nextEvent().asStartElement()
require(start.name.localPart=="INLINEASM") { "missing INLINEASM" }
val label = start.attributes.asSequence().single { it.name.localPart == "LABEL" }.value.ifBlank { null }
val isIr = start.attributes.asSequence().single { it.name.localPart == "IR" }.value.toBoolean()
val text = readText(reader).trim()
require(reader.nextEvent().isEndElement)
return IRInlineAsmChunk(label, text, isIr, null)
}
private fun parseDatatype(type: String, isArray: Boolean): DataType {
@ -251,238 +469,6 @@ class IRFileReader {
}
}
private fun parseSlabs(lines: Iterator<String>): List<StMemorySlab> {
var line = lines.next()
while(line.isBlank())
line = lines.next()
if(line!="<MEMORYSLABS>")
throw IRParseException("invalid MEMORYSLABS")
val slabs = mutableListOf<StMemorySlab>()
val slabPattern = Regex("SLAB (.+) (.+) (.+)")
while(true) {
line = lines.next()
if(line=="</MEMORYSLABS>")
break
// example: "SLAB slabname 4096 0"
val match = slabPattern.matchEntire(line) ?: throw IRParseException("invalid SLAB $line")
val (name, size, align) = match.destructured
slabs.add(StMemorySlab(name, size.toUInt(), align.toUInt(), Position.DUMMY))
}
return slabs
}
private fun parseInitGlobals(lines: Iterator<String>): IRCodeChunk {
var line = lines.next()
while(line.isBlank())
line = lines.next()
if(line!="<INITGLOBALS>")
throw IRParseException("invalid INITGLOBALS")
line = lines.next()
var chunk = IRCodeChunk(null, null)
if(line=="<C>") {
chunk = parseCodeChunk(line, lines)!!
line = lines.next()
}
if(line!="</INITGLOBALS>")
throw IRParseException("missing INITGLOBALS close tag")
return chunk
}
private fun parseBlocksUntilProgramEnd(lines: Iterator<String>): List<IRBlock> {
val blocks = mutableListOf<IRBlock>()
while(true) {
var line = lines.next()
while (line.isBlank())
line = lines.next()
if (line == "</PROGRAM>")
break
blocks.add(parseBlock(line, lines))
}
return blocks
}
private val blockPattern = Regex("<BLOCK NAME=\"(.+)\" ADDRESS=\"(.*)\" ALIGN=\"(.+)\" POS=\"(.+)\">")
private val inlineAsmPattern = Regex("<INLINEASM LABEL=\"(.*)\" IR=\"(.+)\">")
private val bytesPattern = Regex("<BYTES LABEL=\"(.*)\">")
private val asmsubPattern = Regex("<ASMSUB NAME=\"(.+)\" ADDRESS=\"(.+)\" CLOBBERS=\"(.*)\" RETURNS=\"(.*)\" POS=\"(.+)\">")
private val subPattern = Regex("<SUB NAME=\"(.+)\" RETURNTYPE=\"(.+)\" POS=\"(.+)\">")
private val posPattern = Regex("\\[(.+): line (.+) col (.+)-(.+)\\]")
private fun parseBlock(startline: String, lines: Iterator<String>): IRBlock {
var line = startline
if(!line.startsWith("<BLOCK "))
throw IRParseException("invalid BLOCK")
val match = blockPattern.matchEntire(line) ?: throw IRParseException("invalid BLOCK")
val (name, address, align, position) = match.destructured
val addressNum = if(address=="null") null else parseIRValue(address).toUInt()
val block = IRBlock(name, addressNum, IRBlock.BlockAlignment.valueOf(align), parsePosition(position))
while(true) {
line = lines.next()
if(line.isBlank())
continue
if(line=="</BLOCK>")
return block
if(line.startsWith("<SUB ")) {
val sub = parseSubroutine(line, lines)
block += sub
} else if(line.startsWith("<ASMSUB ")) {
val sub = parseAsmSubroutine(line, lines)
block += sub
} else if(line.startsWith("<INLINEASM ")) {
val asm = parseInlineAssembly(line, lines)
block += asm
} else
throw IRParseException("invalid line in BLOCK")
}
}
private fun parseInlineAssembly(startline: String, lines: Iterator<String>): IRInlineAsmChunk {
// <INLINEASM LABEL=optional-label IR=true>
val match = inlineAsmPattern.matchEntire(startline) ?: throw IRParseException("invalid INLINEASM")
val label = match.groupValues[1]
val isIr = match.groupValues[2].toBoolean()
val asmlines = mutableListOf<String>()
var line = lines.next()
while(line!="</INLINEASM>") {
asmlines.add(line)
line = lines.next()
}
return IRInlineAsmChunk(label, asmlines.joinToString("\n"), isIr, null)
}
private fun parseAsmSubroutine(startline: String, lines: Iterator<String>): IRAsmSubroutine {
// <ASMSUB NAME=main.testasmsub ADDRESS=null CLOBBERS=A,Y POS=[examples/test.p8: line 14 col 6-11]>
val match = asmsubPattern.matchEntire(startline) ?: throw IRParseException("invalid ASMSUB")
val (scopedname, address, clobbers, returnSpec, pos) = match.destructured
// parse PARAMS
var line = lines.next()
if(line!="<PARAMS>")
throw IRParseException("missing PARAMS")
val params = mutableListOf<Pair<DataType, RegisterOrStatusflag>>()
while(true) {
line = lines.next()
if(line=="</PARAMS>")
break
val (datatype, regOrSf) = line.split(' ')
val dt = parseDatatype(datatype, datatype.contains('['))
val regsf = parseRegisterOrStatusflag(regOrSf)
params += Pair(dt, regsf)
}
line = lines.next()
val asm = parseInlineAssembly(line, lines)
while(line!="</ASMSUB>")
line = lines.next()
val clobberRegs = if(clobbers.isBlank()) emptyList() else clobbers.split(',').map { CpuRegister.valueOf(it) }
val returns = mutableListOf<Pair<DataType, RegisterOrStatusflag>>()
returnSpec.split(',').forEach{ rs ->
val (regstr, dtstr) = rs.split(':')
val dt = parseDatatype(dtstr, false)
val regsf = parseRegisterOrStatusflag(regstr)
returns.add(Pair(dt, regsf))
}
return IRAsmSubroutine(
scopedname,
if(address=="null") null else parseIRValue(address).toUInt(),
clobberRegs.toSet(),
params,
returns,
asm,
parsePosition(pos)
)
}
private fun parseSubroutine(startline: String, lines: Iterator<String>): IRSubroutine {
// <SUB NAME=main.start.nested.nested2 RETURNTYPE=null POS=[examples/test.p8: line 54 col 14-16]>
val match = subPattern.matchEntire(startline) ?: throw IRParseException("invalid SUB")
val (name, returntype, pos) = match.destructured
val sub = IRSubroutine(name,
parseParameters(lines),
if(returntype=="null") null else parseDatatype(returntype, false),
parsePosition(pos))
while(true) {
val line = lines.next()
if(line=="</SUB>")
return sub
val chunk = if(line.startsWith("<C"))
parseCodeChunk(line, lines)
else if(line.startsWith("<BYTES "))
parseBinaryBytes(line, lines)
else if(line.startsWith("<INLINEASM "))
parseInlineAssembly(line, lines)
else
throw IRParseException("invalid sub child node")
if (chunk == null)
break
else
sub += chunk
}
val line = lines.next()
if(line=="</SUB>")
throw IRParseException("missing SUB close tag")
return sub
}
private fun parseBinaryBytes(startline: String, lines: Iterator<String>): IRInlineBinaryChunk {
val match = bytesPattern.matchEntire(startline) ?: throw IRParseException("invalid BYTES")
val label = match.groupValues[1]
val bytes = mutableListOf<UByte>()
var line = lines.next()
while(line!="</BYTES>") {
line.trimEnd().windowed(size=2, step=2) {
bytes.add(it.toString().toUByte(16))
}
line = lines.next()
}
return IRInlineBinaryChunk(label, bytes, null)
}
private fun parseParameters(lines: Iterator<String>): List<IRSubroutine.IRParam> {
var line = lines.next()
if(line!="<PARAMS>")
throw IRParseException("missing PARAMS")
val params = mutableListOf<IRSubroutine.IRParam>()
while(true) {
line = lines.next()
if(line=="</PARAMS>")
return params
val (datatype, name) = line.split(' ')
val dt = parseDatatype(datatype, datatype.contains('['))
// val orig = variables.single { it.dt==dt && it.name==name}
params.add(IRSubroutine.IRParam(name, dt))
}
}
private fun parseCodeChunk(firstline: String, lines: Iterator<String>): IRCodeChunk? {
if(!firstline.startsWith("<C")) {
if(firstline=="</SUB>")
return null
else
throw IRParseException("invalid or empty <C>ODE chunk")
}
val label = if(firstline.startsWith("<C LABEL=\""))
firstline.split('=', limit = 2)[1].dropLast(1).trim('"')
else
null
val chunk = IRCodeChunk(label, null)
while(true) {
val line = lines.next()
if (line == "</C>")
return chunk
if (line.isBlank() || line.startsWith(';'))
continue
val result = parseIRCodeLine(line, null, mutableMapOf())
result.fold(
ifLeft = {
chunk += it
},
ifRight = {
throw IRParseException("code chunk should not contain a separate label line anymore, this should be the proper label of a new separate chunk")
}
)
}
}
private fun parseRegisterOrStatusflag(regs: String): RegisterOrStatusflag {
var reg: RegisterOrPair? = null
var sf: Statusflag? = null
@ -494,6 +480,8 @@ class IRFileReader {
return RegisterOrStatusflag(reg, sf)
}
private val posPattern = Regex("\\[(.+): line (.+) col (.+)-(.+)\\]")
private fun parsePosition(strpos: String): Position {
// example: "[library:/prog8lib/virtual/textio.p8: line 5 col 2-4]"
val match = posPattern.matchEntire(strpos) ?: throw IRParseException("invalid Position")
@ -501,4 +489,16 @@ class IRFileReader {
return Position(file, line.toInt(), startCol.toInt(), endCol.toInt())
}
private fun readText(reader: XMLEventReader): String {
val sb = StringBuilder()
while(reader.peek().isCharacters) {
sb.append(reader.nextEvent().asCharacters().data)
}
return sb.toString()
}
private fun skipText(reader: XMLEventReader) {
while(reader.peek().isCharacters)
reader.nextEvent()
}
}

View File

@ -67,18 +67,18 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
}
block.asmSubroutines.forEach {
val clobbers = it.clobbers.joinToString(",")
val returns = it.returns.map { (dt, reg) ->
if(reg.registerOrPair!=null) "${reg.registerOrPair}:${dt.toString().lowercase()}"
else "${reg.statusflag}:${dt.toString().lowercase()}"
val returns = it.returns.map { ret ->
if(ret.reg.registerOrPair!=null) "${ret.reg.registerOrPair}:${ret.dt.toString().lowercase()}"
else "${ret.reg.statusflag}:${ret.dt.toString().lowercase()}"
}.joinToString(",")
out.write("<ASMSUB NAME=\"${it.name}\" ADDRESS=\"${it.address?.toHex()}\" CLOBBERS=\"$clobbers\" RETURNS=\"$returns\" POS=\"${it.position}\">\n")
out.write("<PARAMS>\n")
it.parameters.forEach { (dt, regOrSf) ->
val reg = if(regOrSf.registerOrPair!=null) regOrSf.registerOrPair.toString()
else regOrSf.statusflag.toString()
out.write("${dt.toString().lowercase()} $reg\n")
out.write("<ASMPARAMS>\n")
it.parameters.forEach { ret ->
val reg = if(ret.reg.registerOrPair!=null) ret.reg.registerOrPair.toString()
else ret.reg.statusflag.toString()
out.write("${ret.dt.toString().lowercase()} $reg\n")
}
out.write("</PARAMS>\n")
out.write("</ASMPARAMS>\n")
writeInlineAsm(it.asmChunk)
out.write("</ASMSUB>\n")
}

View File

@ -200,6 +200,7 @@ class IRBlock(
val alignment: BlockAlignment,
val position: Position
) {
// TODO not separate lists but just a single list of chunks, like IRSubroutine?
val inlineAssembly = mutableListOf<IRInlineAsmChunk>()
val subroutines = mutableListOf<IRSubroutine>()
val asmSubroutines = mutableListOf<IRAsmSubroutine>()
@ -215,6 +216,7 @@ class IRBlock(
}
operator fun plusAssign(sub: IRAsmSubroutine) { asmSubroutines += sub }
operator fun plusAssign(asm: IRInlineAsmChunk) { inlineAssembly += asm }
operator fun plusAssign(binary: IRInlineBinaryChunk) { TODO("IR BLOCK can't contain inline binary data yet") }
fun isEmpty(): Boolean {
val noAsm = inlineAssembly.isEmpty() || inlineAssembly.all { it.isEmpty() }
@ -256,11 +258,14 @@ class IRAsmSubroutine(
val name: String,
val address: UInt?,
val clobbers: Set<CpuRegister>,
val parameters: List<Pair<DataType, RegisterOrStatusflag>>,
val returns: List<Pair<DataType, RegisterOrStatusflag>>,
val parameters: List<IRAsmParam>,
val returns: List<IRAsmParam>,
val asmChunk: IRInlineAsmChunk,
val position: Position
) {
class IRAsmParam(val reg: RegisterOrStatusflag, val dt: DataType)
init {
require('.' in name) { "subroutine name is not scoped: $name" }
require(!name.startsWith("main.main.")) { "subroutine name invalid main prefix: $name" }