p8ir file format is now valid XML

This commit is contained in:
Irmen de Jong 2022-11-11 23:35:52 +01:00
parent 136a9a39de
commit 9d7b9771c2
10 changed files with 71 additions and 66 deletions

View File

@ -53,5 +53,5 @@ class VirtualMachineDefinition: IMachineDefinition {
} }
interface IVirtualMachineRunner { interface IVirtualMachineRunner {
fun runProgram(irSource: CharSequence) fun runProgram(irSource: String)
} }

View File

@ -11,7 +11,8 @@ class TestLaunchEmu: FunSpec({
test("test launch virtualmachine via target") { test("test launch virtualmachine via target") {
val target = VMTarget() val target = VMTarget()
val tmpfile = kotlin.io.path.createTempFile(suffix=".p8ir") val tmpfile = kotlin.io.path.createTempFile(suffix=".p8ir")
tmpfile.writeText("""<PROGRAM NAME=test> tmpfile.writeText("""<?xml version="1.0" encoding="utf-8"?>
<PROGRAM NAME="test">
<OPTIONS> <OPTIONS>
</OPTIONS> </OPTIONS>
@ -30,7 +31,7 @@ class TestLaunchEmu: FunSpec({
<INITGLOBALS> <INITGLOBALS>
</INITGLOBALS> </INITGLOBALS>
<BLOCK NAME=main ADDRESS=null ALIGN=NONE POS=[unittest: line 42 col 1-9]> <BLOCK NAME="main" ADDRESS="null" ALIGN="NONE" POS="[unittest: line 42 col 1-9]">
</BLOCK> </BLOCK>
</PROGRAM> </PROGRAM>
""") """)

View File

@ -3,6 +3,8 @@ TODO
For next release For next release
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
- fix IR/VM compiler crash: subroutine address in array: uword[] @shared ptrs = [&x1, &x2, &start] + unit test
- IRFileReader should parse the p8ir file with xml parser
- 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) - 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. 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 - 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

@ -1,35 +1,18 @@
%import textio %import textio
%import psg
%zeropage basicsafe %zeropage basicsafe
main { main {
sub start() { sub start() {
ubyte @shared variable uword[] @shared ptrs = [&x1, &x2, &start, 4242, 4242]
ubyte x1
ubyte x2
sub nested() { txt.print_uwhex(ptrs[0], true)
ubyte @shared variable2 txt.spc()
txt.print_uwhex(ptrs[1], true)
variable2 = 33 txt.spc()
nested() txt.print_uwhex(ptrs[2], true)
txt.nl()
sub nested() {
ubyte @shared variable3
variable3 = 33
}
}
nested()
explosion()
}
sub explosion() {
; this subroutine is not used but it is an example of how to make a sound effect using the psg library!
psg.silent()
psg.voice(0, psg.LEFT, 63, psg.NOISE, 0)
psg.voice(1, psg.RIGHT, 63, psg.NOISE, 0)
psg.freq(0, 1000)
psg.freq(1, 2000)
} }
} }

View File

@ -10,19 +10,34 @@ import kotlin.io.path.bufferedReader
class IRFileReader { class IRFileReader {
fun read(irSourceCode: CharSequence): IRProgram { 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()) return parseProgram(irSourceCode.lineSequence().iterator())
} }
fun read(irSourceFile: Path): IRProgram { fun read(irSourceFile: Path): IRProgram {
println("Reading intermediate representation from $irSourceFile") println("Reading intermediate representation from $irSourceFile")
irSourceFile.bufferedReader().use { reader ->
// 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()) return parseProgram(reader.lineSequence().iterator())
} }
} }
private fun parseProgram(lines: Iterator<String>): IRProgram { private fun parseProgram(lines: Iterator<String>): IRProgram {
val programPattern = Regex("<PROGRAM NAME=(.+)>") 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 line = lines.next()
val match = programPattern.matchEntire(line) ?: throw IRParseException("invalid PROGRAM") val match = programPattern.matchEntire(line) ?: throw IRParseException("invalid PROGRAM")
val programName = match.groups[1]!!.value val programName = match.groups[1]!!.value
@ -171,7 +186,7 @@ class IRFileReader {
} else { } else {
bss = false bss = false
initArray = value.split(',').map { initArray = value.split(',').map {
if (it.startsWith('&')) if (it.startsWith('@'))
StArrayElement(null, it.drop(1).split('.')) StArrayElement(null, it.drop(1).split('.'))
else else
StArrayElement(parseIRValue(it).toDouble(), null) StArrayElement(parseIRValue(it).toDouble(), null)
@ -194,7 +209,7 @@ class IRFileReader {
if(line!="<MEMORYMAPPEDVARIABLES>") if(line!="<MEMORYMAPPEDVARIABLES>")
throw IRParseException("invalid MEMORYMAPPEDVARIABLES") throw IRParseException("invalid MEMORYMAPPEDVARIABLES")
val memvars = mutableListOf<StMemVar>() val memvars = mutableListOf<StMemVar>()
val mappedPattern = Regex("&(.+?)(\\[.+?\\])? (.+)=(.+)") val mappedPattern = Regex("@(.+?)(\\[.+?\\])? (.+)=(.+)")
while(true) { while(true) {
line = lines.next() line = lines.next()
if(line=="</MEMORYMAPPEDVARIABLES>") if(line=="</MEMORYMAPPEDVARIABLES>")
@ -286,11 +301,11 @@ class IRFileReader {
return blocks return blocks
} }
private val blockPattern = Regex("<BLOCK NAME=(.+) ADDRESS=(.*) ALIGN=(.+) POS=(.+)>") private val blockPattern = Regex("<BLOCK NAME=\"(.+)\" ADDRESS=\"(.*)\" ALIGN=\"(.+)\" POS=\"(.+)\">")
private val inlineAsmPattern = Regex("<INLINEASM LABEL=(.*) IR=(.+)>") private val inlineAsmPattern = Regex("<INLINEASM LABEL=\"(.*)\" IR=\"(.+)\">")
private val bytesPattern = Regex("<BYTES LABEL=(.*)>") private val bytesPattern = Regex("<BYTES LABEL=\"(.*)\">")
private val asmsubPattern = Regex("<ASMSUB NAME=(.+) ADDRESS=(.+) CLOBBERS=(.*) RETURNS=(.*) POS=(.+)>") private val asmsubPattern = Regex("<ASMSUB NAME=\"(.+)\" ADDRESS=\"(.+)\" CLOBBERS=\"(.*)\" RETURNS=\"(.*)\" POS=\"(.+)\">")
private val subPattern = Regex("<SUB NAME=(.+) RETURNTYPE=(.+) POS=(.+)>") private val subPattern = Regex("<SUB NAME=\"(.+)\" RETURNTYPE=\"(.+)\" POS=\"(.+)\">")
private val posPattern = Regex("\\[(.+): line (.+) col (.+)-(.+)\\]") private val posPattern = Regex("\\[(.+): line (.+) col (.+)-(.+)\\]")
private fun parseBlock(startline: String, lines: Iterator<String>): IRBlock { private fun parseBlock(startline: String, lines: Iterator<String>): IRBlock {
@ -445,8 +460,8 @@ class IRFileReader {
else else
throw IRParseException("invalid or empty <C>ODE chunk") throw IRParseException("invalid or empty <C>ODE chunk")
} }
val label = if(firstline.startsWith("<C LABEL=")) val label = if(firstline.startsWith("<C LABEL=\""))
firstline.split('=', limit = 2)[1].dropLast(1) firstline.split('=', limit = 2)[1].dropLast(1).trim('"')
else else
null null
val chunk = IRCodeChunk(label, null) val chunk = IRCodeChunk(label, null)

View File

@ -8,14 +8,15 @@ import kotlin.io.path.div
class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) { class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
private val outfile = outfileOverride ?: (irProgram.options.outputDir / ("${irProgram.name}.p8ir")) private val outfile = outfileOverride ?: (irProgram.options.outputDir / ("${irProgram.name}.p8ir"))
private val out = outfile.bufferedWriter() private val out = outfile.bufferedWriter(charset=Charsets.UTF_8)
private var numChunks = 0 private var numChunks = 0
private var numInstr = 0 private var numInstr = 0
fun write(): Path { fun write(): Path {
println("Writing intermediate representation to $outfile") println("Writing intermediate representation to $outfile")
out.write("<PROGRAM NAME=${irProgram.name}>\n") out.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
out.write("<PROGRAM NAME=\"${irProgram.name}\">\n")
writeOptions() writeOptions()
writeAsmSymbols() writeAsmSymbols()
writeVariableAllocations() writeVariableAllocations()
@ -44,12 +45,12 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
private fun writeBlocks() { private fun writeBlocks() {
irProgram.blocks.forEach { block -> irProgram.blocks.forEach { block ->
out.write("\n<BLOCK NAME=${block.name} ADDRESS=${block.address?.toHex()} ALIGN=${block.alignment} POS=${block.position}>\n") out.write("\n<BLOCK NAME=\"${block.name}\" ADDRESS=\"${block.address?.toHex()}\" ALIGN=\"${block.alignment}\" POS=\"${block.position}\">\n")
block.inlineAssembly.forEach { block.inlineAssembly.forEach {
writeInlineAsm(it) writeInlineAsm(it)
} }
block.subroutines.forEach { block.subroutines.forEach {
out.write("<SUB NAME=${it.name} RETURNTYPE=${it.returnType.toString().lowercase()} POS=${it.position}>\n") out.write("<SUB NAME=\"${it.name}\" RETURNTYPE=\"${it.returnType.toString().lowercase()}\" POS=\"${it.position}\">\n")
out.write("<PARAMS>\n") out.write("<PARAMS>\n")
it.parameters.forEach { param -> out.write("${getTypeString(param.dt)} ${param.name}\n") } it.parameters.forEach { param -> out.write("${getTypeString(param.dt)} ${param.name}\n") }
out.write("</PARAMS>\n") out.write("</PARAMS>\n")
@ -70,7 +71,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
if(reg.registerOrPair!=null) "${reg.registerOrPair}:${dt.toString().lowercase()}" if(reg.registerOrPair!=null) "${reg.registerOrPair}:${dt.toString().lowercase()}"
else "${reg.statusflag}:${dt.toString().lowercase()}" else "${reg.statusflag}:${dt.toString().lowercase()}"
}.joinToString(",") }.joinToString(",")
out.write("<ASMSUB NAME=${it.name} ADDRESS=${it.address?.toHex()} CLOBBERS=$clobbers RETURNS=$returns POS=${it.position}>\n") out.write("<ASMSUB NAME=\"${it.name}\" ADDRESS=\"${it.address?.toHex()}\" CLOBBERS=\"$clobbers\" RETURNS=\"$returns\" POS=\"${it.position}\">\n")
out.write("<PARAMS>\n") out.write("<PARAMS>\n")
it.parameters.forEach { (dt, regOrSf) -> it.parameters.forEach { (dt, regOrSf) ->
val reg = if(regOrSf.registerOrPair!=null) regOrSf.registerOrPair.toString() val reg = if(regOrSf.registerOrPair!=null) regOrSf.registerOrPair.toString()
@ -87,7 +88,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
private fun writeCodeChunk(chunk: IRCodeChunk) { private fun writeCodeChunk(chunk: IRCodeChunk) {
if(chunk.label!=null) if(chunk.label!=null)
out.write("<C LABEL=${chunk.label}>\n") out.write("<C LABEL=\"${chunk.label}\">\n")
else else
out.write("<C>\n") out.write("<C>\n")
chunk.instructions.forEach { instr -> chunk.instructions.forEach { instr ->
@ -99,7 +100,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
} }
private fun writeInlineBytes(chunk: IRInlineBinaryChunk) { private fun writeInlineBytes(chunk: IRInlineBinaryChunk) {
out.write("<BYTES LABEL=${chunk.label ?: ""}>\n") out.write("<BYTES LABEL=\"${chunk.label ?: ""}\">\n")
chunk.data.withIndex().forEach {(index, byte) -> chunk.data.withIndex().forEach {(index, byte) ->
out.write(byte.toString(16).padStart(2,'0')) out.write(byte.toString(16).padStart(2,'0'))
if(index and 63 == 63 && index < chunk.data.size-1) if(index and 63 == 63 && index < chunk.data.size-1)
@ -109,7 +110,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
} }
private fun writeInlineAsm(chunk: IRInlineAsmChunk) { private fun writeInlineAsm(chunk: IRInlineAsmChunk) {
out.write("<INLINEASM LABEL=${chunk.label ?: ""} IR=${chunk.isIR}>\n") out.write("<INLINEASM LABEL=\"${chunk.label ?: ""}\" IR=\"${chunk.isIR}\">\n")
out.write(chunk.assembly) out.write(chunk.assembly)
out.write("\n</INLINEASM>\n") out.write("\n</INLINEASM>\n")
} }
@ -157,7 +158,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
if(it.number!=null) if(it.number!=null)
it.number!!.toInt().toHex() it.number!!.toInt().toHex()
else else
"&${it.addressOf!!.joinToString(".")}" "@${it.addressOf!!.joinToString(".")}"
} }
} else { } else {
"" // array will be zero'd out at program start "" // array will be zero'd out at program start
@ -172,7 +173,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
out.write("\n<MEMORYMAPPEDVARIABLES>\n") out.write("\n<MEMORYMAPPEDVARIABLES>\n")
for (variable in irProgram.st.allMemMappedVariables()) { for (variable in irProgram.st.allMemMappedVariables()) {
val typeStr = getTypeString(variable) val typeStr = getTypeString(variable)
out.write("&$typeStr ${variable.name}=${variable.address.toHex()}\n") out.write("@$typeStr ${variable.name}=${variable.address.toHex()}\n")
} }
out.write("</MEMORYMAPPEDVARIABLES>\n") out.write("</MEMORYMAPPEDVARIABLES>\n")

View File

@ -521,8 +521,8 @@ data class InstructionFormat(val datatype: IRDataType?,
/* /*
<X = X is not modified (input/readonly value) <X = X is not modified (input/readonly value)
>X = X is overwritten with output value (output value) >X = X is overwritten with output value (output value)
<>X = X is modified (input + output) <>X = X is modified (read as input + written as output)
TODO: also encode if memory is read/written/modified? TODO: also encode if *memory* is read/written/modified?
*/ */
@Suppress("BooleanLiteralArgument") @Suppress("BooleanLiteralArgument")
val instructionFormats = mutableMapOf( val instructionFormats = mutableMapOf(

View File

@ -33,14 +33,16 @@ class TestIRFileInOut: FunSpec({
val writer = IRFileWriter(program, null) val writer = IRFileWriter(program, null)
val generatedFile = writer.write() val generatedFile = writer.write()
val lines = generatedFile.readLines() val lines = generatedFile.readLines()
lines.first() shouldBe "<PROGRAM NAME=unittest-irwriter>" lines[0] shouldBe "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
lines[1] shouldBe "<PROGRAM NAME=\"unittest-irwriter\">"
lines.last() shouldBe "</PROGRAM>" lines.last() shouldBe "</PROGRAM>"
generatedFile.deleteExisting() generatedFile.deleteExisting()
lines.size shouldBeGreaterThan 20 lines.size shouldBeGreaterThan 20
} }
test("test IR reader") { test("test IR reader") {
val source="""<PROGRAM NAME=test-ir-reader> val source="""<?xml version="1.0" encoding="utf-8"?>
<PROGRAM NAME="test-ir-reader">
<OPTIONS> <OPTIONS>
compTarget=virtual compTarget=virtual
output=PRG output=PRG
@ -59,7 +61,7 @@ uword sys.wait.jiffies= zp=DONTCARE
</VARIABLES> </VARIABLES>
<MEMORYMAPPEDVARIABLES> <MEMORYMAPPEDVARIABLES>
&uword cx16.r0=65282 @uword cx16.r0=65282
</MEMORYMAPPEDVARIABLES> </MEMORYMAPPEDVARIABLES>
<MEMORYSLABS> <MEMORYSLABS>
@ -71,22 +73,22 @@ load.b r1,42
</C> </C>
</INITGLOBALS> </INITGLOBALS>
<BLOCK NAME=main ADDRESS=null ALIGN=NONE POS=[examples/test.p8: line 2 col 2-5]> <BLOCK NAME="main" ADDRESS="null" ALIGN="NONE" POS="[examples/test.p8: line 2 col 2-5]">
<SUB NAME=main.start RETURNTYPE=null POS=[examples/test.p8: line 4 col 6-8]> <SUB NAME="main.start" RETURNTYPE="null" POS="[examples/test.p8: line 4 col 6-8]">
<PARAMS> <PARAMS>
</PARAMS> </PARAMS>
<C LABEL=main.start> <C LABEL="main.start">
return return
</C> </C>
</SUB> </SUB>
</BLOCK> </BLOCK>
<BLOCK NAME=sys ADDRESS=null ALIGN=NONE POS=[library:/prog8lib/virtual/syslib.p8: line 3 col 2-4]> <BLOCK NAME="sys" ADDRESS="null" ALIGN="NONE" POS="[library:/prog8lib/virtual/syslib.p8: line 3 col 2-4]">
<SUB NAME=sys.wait RETURNTYPE=null POS=[library:/prog8lib/virtual/syslib.p8: line 15 col 6-8]> <SUB NAME="sys.wait" RETURNTYPE="null" POS="[library:/prog8lib/virtual/syslib.p8: line 15 col 6-8]">
<PARAMS> <PARAMS>
uword sys.wait.jiffies uword sys.wait.jiffies
</PARAMS> </PARAMS>
<INLINEASM LABEL=sys.wait IR=true POS=[library:/prog8lib/virtual/syslib.p8: line 17 col 10-13]> <INLINEASM LABEL="sys.wait" IR="true" POS="[library:/prog8lib/virtual/syslib.p8: line 17 col 10-13]">
loadm.w r0,sys.wait.jiffies loadm.w r0,sys.wait.jiffies
syscall 13 syscall 13
</INLINEASM> </INLINEASM>

View File

@ -2155,11 +2155,11 @@ class VirtualMachine(irProgram: IRProgram) {
// probably called via reflection // probably called via reflection
class VmRunner: IVirtualMachineRunner { class VmRunner: IVirtualMachineRunner {
override fun runProgram(irSource: CharSequence) { override fun runProgram(irSource: String) {
runAndTestProgram(irSource) { /* no tests */ } runAndTestProgram(irSource) { /* no tests */ }
} }
fun runAndTestProgram(irSource: CharSequence, test: (VirtualMachine) -> Unit) { fun runAndTestProgram(irSource: String, test: (VirtualMachine) -> Unit) {
val irProgram = IRFileReader().read(irSource) val irProgram = IRFileReader().read(irSource)
val vm = VirtualMachine(irProgram) val vm = VirtualMachine(irProgram)
vm.run() vm.run()

View File

@ -120,7 +120,8 @@ class TestVm: FunSpec( {
test("vmrunner") { test("vmrunner") {
val runner = VmRunner() val runner = VmRunner()
val irSource="""<PROGRAM NAME=test> val irSource="""<?xml version="1.0" encoding="utf-8"?>
<PROGRAM NAME="test">
<OPTIONS> <OPTIONS>
</OPTIONS> </OPTIONS>
@ -139,7 +140,7 @@ class TestVm: FunSpec( {
<INITGLOBALS> <INITGLOBALS>
</INITGLOBALS> </INITGLOBALS>
<BLOCK NAME=main ADDRESS=null ALIGN=NONE POS=[unittest: line 42 col 1-9]> <BLOCK NAME="main" ADDRESS="null" ALIGN="NONE" POS="[unittest: line 42 col 1-9]">
</BLOCK> </BLOCK>
</PROGRAM> </PROGRAM>
""" """