Map scripts now being built as PLASMA source which is then compiled, instead of direct-to-bytecode. This will be much easier to maintain once it's fully working.

This commit is contained in:
Martin Haye 2016-02-03 08:50:22 -08:00
parent c0e98cd6c4
commit 042450d4ce

View File

@ -906,13 +906,11 @@ class PackPartitions
def name = "mapScript$num" def name = "mapScript$num"
//println "Packing scripts for map $mapName, to module $num." //println "Packing scripts for map $mapName, to module $num."
def scriptDir = "build/"
ScriptModule module = new ScriptModule() ScriptModule module = new ScriptModule()
module.packScripts(mapName, mapEl.scripts ? mapEl.scripts[0] : [], module.packScripts(mapName, new File(new File(scriptDir), name+".pla"), mapEl.scripts ? mapEl.scripts[0] : [],
totalWidth, totalHeight, xRange, yRange) totalWidth, totalHeight, xRange, yRange)
compileModule(name, scriptDir)
modules[name] = [num:num, buf:wrapByteList(module.data)]
bytecodes[name] = [num:num, buf:wrapByteList(module.bytecode)]
fixups[name] = [num:num, buf:wrapByteList(module.fixups)]
return [num, module.locationsWithTriggers] return [num, module.locationsWithTriggers]
} }
@ -1890,55 +1888,32 @@ class PackPartitions
class ScriptModule class ScriptModule
{ {
def data = [] PrintWriter out
def bytecode = []
def fixups = []
def nScripts = 0
def nStringBytes = 0
def locationsWithTriggers = [] as Set def locationsWithTriggers = [] as Set
def scriptNames = [:]
def indent = 0
def vec_setScriptInfo = 0x1F00 def emitString(inStr)
def vec_pushAuxStr = 0x1F03
def vec_displayStr = 0x1F06
def vec_displayStrNL = 0x1F09
def vec_getYN = 0x1F0C
def vec_setMap = 0x1F0F
def vec_setSky = 0x1F12
def vec_setGround = 0x1F15
def vec_teleport = 0x1F18
def vec_setPortrait = 0x1F1B
def vec_clrPortrait = 0x1F1E
def vec_moveBackward = 0x1F21
def vec_getCharacter = 0x1F24
def vec_clrTextWindow = 0x1F27
def emitAuxString(inStr)
{ {
emitCodeByte(0x54) // CALL out << '\"'
emitCodeWord(vec_pushAuxStr) def prev = '\0'
def buf = new StringBuilder()
def prev = ' '
inStr.each { ch -> inStr.each { ch ->
if (ch == '^') { if (ch == '^') {
if (prev == '^') if (prev == '^')
buf.append(ch) out << ch
} }
else if (ch == '\"')
out << "\\\""
else if (prev == '^') { else if (prev == '^') {
def cp = Character.codePointAt(ch.toUpperCase(), 0) def cp = Character.codePointAt(ch.toUpperCase(), 0)
if (cp > 64 && cp < 96) if (cp > 64 && cp < 96)
buf.appendCodePoint(cp - 64) out << "\\\$" << String.format("%02X", cp - 64)
} }
else else
buf.append(ch) out << ch
prev = ch prev = ch
} }
def str = buf.toString() out << '\"'
assert str.size() < 256 : "String too long, max is 255 characters: $str"
emitCodeByte(str.size())
str.each { ch -> emitCodeByte((byte)ch) }
nStringBytes += str.size() + 1
} }
def getScriptName(script) def getScriptName(script)
@ -1947,8 +1922,11 @@ class PackPartitions
return null return null
def blk = script.block[0] def blk = script.block[0]
if (blk.field.size() == 0) if (blk.field.size() == 0) {
if (scriptNames.containsKey(script))
return scriptNames[script]
return null return null
}
assert blk.field[0].@name == "NAME" assert blk.field[0].@name == "NAME"
return blk.field[0].text() return blk.field[0].text()
@ -1958,72 +1936,59 @@ class PackPartitions
* Pack scripts from a map. Either the whole map, or optionally just an X and Y * Pack scripts from a map. Either the whole map, or optionally just an X and Y
* bounded section of it. * bounded section of it.
*/ */
def packScripts(mapName, inScripts, maxX, maxY, xRange = null, yRange = null) def packScripts(mapName, outFile, inScripts, maxX, maxY, xRange = null, yRange = null)
{ {
// If we're only processing a section of the map, make sure this script is out = new PrintWriter(new FileWriter(outFile))
// referenced within that section. out << "// Generated code - DO NOT MODIFY BY HAND\n\n"
// out << "include \"../src/plasma/gamelib.plh\"\n"
out << "include \"../src/plasma/playtype.plh\"\n"
out << "include \"../src/plasma/gen_images.plh\"\n\n"
// Determine which scripts are referenced in the specified section of the map.
def initScript
def scripts = [] def scripts = []
inScripts.script.eachWithIndex { script, idx -> inScripts.script.eachWithIndex { script, idx ->
def name = getScriptName(script) def name = getScriptName(script)
if (name != null && name.toLowerCase() == "init") if (name != null && name.toLowerCase() == "init") {
scripts << script initScript = script
}
else if (script.locationTrigger.any { trig -> else if (script.locationTrigger.any { trig ->
(!xRange || trig.@x.toInteger() in xRange) && (!xRange || trig.@x.toInteger() in xRange) &&
(!yRange || trig.@y.toInteger() in yRange) }) (!yRange || trig.@y.toInteger() in yRange) })
{
scripts << script scripts << script
scriptNames[script] = "trig_$idx"
}
} }
nScripts = scripts.script.size()
// Even if there were no scripts, we still need an init to display // Even if there were no scripts, we still need an init to display
// the map name. // the map name.
makeStubs() makeTriggerTbl(scripts, xRange, yRange)
scripts.eachWithIndex { script, idx -> scripts.eachWithIndex { script, idx ->
packScript(idx, script) packScript(idx, script)
} }
makeInit(mapName, scripts, xRange, yRange, maxX, maxY) makeInit(mapName, initScript, maxX, maxY)
emitFixupByte(0xFF)
//println " Code stats: data=${data.size}, bytecode=${bytecode.size} (str=$nStringBytes), fixups=${fixups.size}" out.close()
//println "data: $data"
//println "bytecode: $bytecode"
//println "fixups: $fixups"
} }
def makeStubs() def outIndented(str) {
{ out << (" " * indent) << str
// Emit a stub for each function, including the init function
(0..nScripts).each { it ->
emitDataByte(0x20) // JSR
emitDataWord(0x3DC) // Aux mem interp ($3DC)
emitDataWord(0) // Placeholder for the bytecode offset
}
}
def startFunc(scriptNum)
{
def fixAddr = (scriptNum * 5) + 3
assert data[fixAddr] == 0 && data[fixAddr+1] == 0
data[fixAddr] = (byte)(bytecodeAddr() & 0xFF)
data[fixAddr+1] = (byte)((bytecodeAddr() >> 8) & 0xFF)
} }
def packScript(scriptNum, script) def packScript(scriptNum, script)
{ {
def name = getScriptName(script)
if (name.toLowerCase() == "init") // this special script gets processed later
return
//println " Script '$name'" //println " Script '$name'"
withContext("script '$name'") withContext("script $scriptNum")
{ {
if (script.block.size() == 0) { if (script.block.size() == 0) {
printWarning("empty script found; skipping.") printWarning("empty script found; skipping.")
return return
} }
// Record the function's start address in its corresponding stub // Record the function's name and start its definition
startFunc(scriptNum+1) out << "def script$scriptNum()\n"
indent = 1
// Process the code inside it // Process the code inside it
def proc = script.block[0] def proc = script.block[0]
@ -2038,17 +2003,10 @@ class PackPartitions
printWarning "empty statement found; skipping." printWarning "empty statement found; skipping."
// And complete the function // And complete the function
finishFunc() out << "end\n\n"
} }
} }
def finishFunc()
{
// Finish off the function with a return value and return opcode
emitCodeByte(0) // ZERO
emitCodeByte(0x5C) // RET
}
def packBlock(blk) def packBlock(blk)
{ {
withContext("${blk.@type}") { withContext("${blk.@type}") {
@ -2060,7 +2018,7 @@ class PackPartitions
case 'text_clear_window': case 'text_clear_window':
packClearWindow(blk); break packClearWindow(blk); break
case 'text_getanykey': case 'text_getanykey':
packGetAnyKey(); break packGetAnyKey(blk); break
case 'controls_if': case 'controls_if':
packIfStmt(blk); break packIfStmt(blk); break
case 'events_set_map': case 'events_set_map':
@ -2086,60 +2044,15 @@ class PackPartitions
blk.next.each { it.block.each { packBlock(it) } } blk.next.each { it.block.each { packBlock(it) } }
} }
def dataAddr() def getSingle(els, name = null, type = null)
{ {
return data.size() assert els.size() == 1
} def first = els[0]
if (name)
def bytecodeAddr() assert first.@name == name
{ if (type)
return bytecode.size() assert first.@type == type
} return first
def emitDataByte(b)
{
assert b <= 255
data.add((byte)(b & 0xFF))
}
def emitDataWord(w)
{
emitDataByte(w & 0xFF)
emitDataByte(w >> 8)
}
def emitCodeByte(b)
{
assert b <= 255
bytecode.add((byte)(b & 0xFF))
}
def emitCodeWord(w)
{
emitCodeByte(w & 0xFF)
emitCodeByte(w >> 8)
}
def emitFixupByte(b)
{
assert b <= 255
fixups.add((byte)(b & 0xFF))
}
def emitDataFixup(toAddr)
{
// Src addr is reversed (i.e. it's hi then lo)
emitFixupByte(dataAddr() >> 8)
emitFixupByte(dataAddr() & 0xFF)
emitDataWord(toAddr)
}
def emitCodeFixup(toAddr)
{
// Src addr is reversed (i.e. it's hi then lo), and marked by hi-bit flag.
emitFixupByte((bytecodeAddr() >> 8) | 0x80)
emitFixupByte(bytecodeAddr() & 0xFF)
emitCodeWord(toAddr)
} }
def packTextPrint(blk) def packTextPrint(blk)
@ -2148,40 +2061,22 @@ class PackPartitions
printWarning "empty text_print block, skipping." printWarning "empty text_print block, skipping."
return return
} }
def val = blk.value[0] def text = getSingle(getSingle(getSingle(blk.value, 'VALUE').block, null, 'text').field, 'TEXT').text()
assert val.@name == 'VALUE' outIndented("${blk.@type == 'text_print' ? 'scriptDisplayStr' : 'scriptDisplayStrNL'}(")
assert val.block.size() == 1 emitString(text)
def valBlk = val.block[0] out << ")\n"
assert valBlk.@type == 'text'
assert valBlk.field.size() == 1
def fld = valBlk.field[0]
assert fld.@name == 'TEXT'
def text = fld.text()
//println " text: '$text'"
emitAuxString(text)
emitCodeByte(0x54) // CALL
emitCodeWord(blk.@type == 'text_print' ? vec_displayStr : vec_displayStrNL)
emitCodeByte(0x30) // DROP
} }
def packClearWindow(blk) def packClearWindow(blk)
{ {
assert blk.value.size() == 0 assert blk.value.size() == 0
//println " clearWindow" outIndented("clearWindow()\n")
emitCodeByte(0x54) // CALL
emitCodeWord(vec_clrTextWindow)
emitCodeByte(0x30) // DROP
} }
def packGetAnyKey(blk) def packGetAnyKey(blk)
{ {
//println " get any key" assert blk.value.size() == 0
outIndented("getUpperKey()\n")
emitCodeByte(0x54) // CALL
emitCodeWord(vec_getCharacter)
emitCodeByte(0x30) // DROP
} }
def packIfStmt(blk) def packIfStmt(blk)
@ -2190,152 +2085,74 @@ class PackPartitions
printWarning "missing condition; skipping." printWarning "missing condition; skipping."
return return
} }
assert blk.value.size() == 1 def cond = getSingle(blk.value, 'IF0')
def cond = blk.value[0] outIndented("if (")
assert cond.@name == 'IF0' def ctype = getSingle(cond.block)
assert cond.block.size() == 1 switch (ctype.@type) {
assert cond.block[0].@type == 'text_getboolean' case 'text_getboolean':
out << "getYN()"
//print " Conditional on getboolean," break
default:
emitCodeByte(0x54) // CALL assert false : "Conditional on ${ctype.@type} not yet implemented."
emitCodeWord(vec_getYN) }
emitCodeByte(0x4C) // BRFS out << ")\n"
def branchStart = bytecodeAddr() ++indent
emitCodeWord(0) // placeholder until we know how far to jump over packBlock(getSingle(getSingle(blk.statement).block))
--indent
assert blk.statement.size() == 1 outIndented("fin\n")
def doStmt = blk.statement[0]
assert doStmt.block.size() == 1
packBlock(doStmt.block[0])
// Now calculate and fix the relative branch
def branchEnd = bytecodeAddr()
def rel = branchEnd - branchStart
bytecode[branchStart] = (byte)(rel & 0xFF)
bytecode[branchStart+1] = (byte)((rel >> 8) & 0xFF)
} }
def packSetMap(blk) def packSetMap(blk)
{ {
def mapNum, x=0, y=0, facing=0 assert blk.field.size() == 4
assert blk.field[0].@name == 'NAME'
blk.field.eachWithIndex { fld, idx -> assert blk.field[1].@name == 'X'
switch (fld.@name) assert blk.field[2].@name == 'Y'
{ assert blk.field[3].@name == 'FACING'
case 'NAME': def mapName = blk.field[0].text()
def mapName = fld.text() def mapNum = mapNames[mapName]
mapNum = mapNames[mapName]
if (!mapNum) { if (!mapNum) {
printWarning "map '$mapName' not found; skipping set_map." printWarning "map '$mapName' not found; skipping set_map."
return return
} }
break
case 'X':
x = fld.text().toInteger()
break
case 'Y':
y = fld.text().toInteger()
break
case 'FACING':
facing = fld.text().toInteger()
assert facing >= 0 && facing <= 15
break
default:
assert false : "Unknown field ${fld.@name}"
}
}
//println " Set map to '$mapName' (num $mapNum)"
emitCodeByte(0x2A) // CB
assert mapNum[0] == '2D' || mapNum[0] == '3D' assert mapNum[0] == '2D' || mapNum[0] == '3D'
emitCodeByte(mapNum[0] == '2D' ? 0 : 1) def x = blk.field[1].text().toInteger()
emitCodeByte(0x2A) // CB def y = blk.field[2].text().toInteger()
emitCodeByte(mapNum[1]) def facing = blk.field[3].text().toInteger()
emitCodeByte(0x2C) // CW assert facing >= 0 && facing <= 15
emitCodeWord(x)
emitCodeByte(0x2C) // CW outIndented("queue_setMap(${mapNum[0] == '2D' ? 0 : 1}, ${mapNum[1]}, $x, $y)\n")
emitCodeWord(y)
emitCodeByte(0x2A) // CB
emitCodeByte(facing)
emitCodeByte(0x54) // CALL
emitCodeWord(vec_setMap)
emitCodeByte(0x30) // DROP
} }
def packSetPortrait(blk) def packSetPortrait(blk)
{ {
def portraitNum, portraitName def portraitName = getSingle(blk.field, 'NAME').text()
blk.field.eachWithIndex { fld, idx ->
switch (fld.@name)
{
case 'NAME':
portraitName = fld.text()
def portrait = portraits[portraitName] def portrait = portraits[portraitName]
if (!portrait) { if (!portrait) {
printWarning "portrait '$portraitName' not found; skipping set_portrait." printWarning "portrait '$portraitName' not found; skipping set_portrait."
return return
} }
portraitNum = portrait.num outIndented("setPortrait(${portrait.num})\n")
break
default:
assert false : "Unknown field ${fld.@name}"
}
}
emitCodeByte(0x2A) // CB
emitCodeByte(portraitNum)
emitCodeByte(0x54) // CALL
emitCodeWord(vec_setPortrait)
emitCodeByte(0x30) // DROP
} }
def packClrPortrait(blk) def packClrPortrait(blk)
{ {
assert blk.field.size() == 0 assert blk.field.size() == 0
outIndented("clearPortrait()\n")
emitCodeByte(0x54) // CALL
emitCodeWord(vec_clrPortrait)
emitCodeByte(0x30) // DROP
} }
def packSetSky(blk) def packSetSky(blk)
{ {
assert blk.field.size() == 1 def color = getSingle(blk.field, 'COLOR').text().toInteger()
def fld = blk.field[0]
assert fld.@name == 'COLOR'
def color = fld.text().toInteger()
assert color >= 0 && color <= 17 assert color >= 0 && color <= 17
//println " Set sky to $color" outIndented("setSky($color)\n")
emitCodeByte(0x2A) // CB
emitCodeByte(color)
emitCodeByte(0x54) // CALL
emitCodeWord(vec_setSky)
emitCodeByte(0x30) // DROP
} }
def packSetGround(blk) def packSetGround(blk)
{ {
assert blk.field.size() == 1 def color = getSingle(blk.field, 'COLOR').text().toInteger()
def fld = blk.field[0]
assert fld.@name == 'COLOR'
def color = fld.text().toInteger()
assert color >= 0 && color <= 17 assert color >= 0 && color <= 17
//println " Set ground to $color" outIndented("setGround($color)\n")
emitCodeByte(0x2A) // CB
emitCodeByte(color)
emitCodeByte(0x54) // CALL
emitCodeWord(vec_setGround)
emitCodeByte(0x30) // DROP
} }
def packTeleport(blk) def packTeleport(blk)
@ -2348,52 +2165,25 @@ class PackPartitions
def y = blk.field[1].text().toInteger() def y = blk.field[1].text().toInteger()
def facing = blk.field[2].text().toInteger() def facing = blk.field[2].text().toInteger()
assert facing >= 0 && facing <= 15 assert facing >= 0 && facing <= 15
//println " Teleport to ($x,$y) facing $facing" outIndented("queue_teleport($x, $y, $facing)\n")
emitCodeByte(0x2C) // CW
emitCodeWord(x)
emitCodeByte(0x2C) // CW
emitCodeWord(y)
emitCodeByte(0x2A) // CB
emitCodeByte(facing)
emitCodeByte(0x54) // CALL
emitCodeWord(vec_teleport)
emitCodeByte(0x30) // DROP
} }
def packMoveBackward(blk) def packMoveBackward(blk)
{ {
assert blk.field.size() == 0 assert blk.field.size() == 0
//println " Move backward" outIndented("moveBackward()\n")
emitCodeByte(0x54) // CALL
emitCodeWord(vec_moveBackward)
emitCodeByte(0x30) // DROP
} }
def makeInit(mapName, scripts, xRange, yRange, maxX, maxY) def makeTriggerTbl(scripts, xRange, yRange)
{ {
//println " Script: special 'init'" // Emit a predefinition for each function
startFunc(0) scripts.eachWithIndex { script, idx ->
out << "predef script$idx\n"
}
// Emit the code the user has stored for the init script. While we're scanning, // Collate all the matching location triggers into a sorted map.
// might as well also collate all the location triggers into a sorted map.
//
TreeMap triggers = [:] TreeMap triggers = [:]
scripts.eachWithIndex { script, idx -> scripts.eachWithIndex { script, idx ->
def name = getScriptName(script)
if (name != null && name.toLowerCase() == "init")
{
if (script.block.size() == 1) {
def proc = script.block[0]
assert proc.@type == "procedures_defreturn"
assert proc.statement.size() == 1
def stmt = proc.statement[0]
assert stmt.@name == "STACK"
stmt.block.each { packBlock(it) }
}
}
else {
script.locationTrigger.each { trig -> script.locationTrigger.each { trig ->
def x = trig.@x.toInteger() def x = trig.@x.toInteger()
def y = trig.@y.toInteger() def y = trig.@y.toInteger()
@ -2407,64 +2197,47 @@ class PackPartitions
triggers[y] = [:] as TreeMap triggers[y] = [:] as TreeMap
if (!triggers[y][x]) if (!triggers[y][x])
triggers[y][x] = [] triggers[y][x] = []
triggers[y][x].add((idx+1) * 5) // address of function triggers[y][x].add("script$idx")
}
} }
} }
} }
// Process the map name // Now output code for the table. First comes the X
def shortName = mapName.replaceAll(/[\s-]*[23][dD][-0-9]*$/, '').take(16)
// Code to register the map name, trigger table, and map extent.
emitAuxString(shortName)
emitCodeByte(0x26) // LA
emitCodeFixup(dataAddr())
emitCodeByte(0x2C) // CW
emitCodeWord(maxX)
emitCodeByte(0x2C) // CW
emitCodeWord(maxY)
emitCodeByte(0x54) // CALL
emitCodeWord(vec_setScriptInfo)
emitCodeByte(0x30) // DROP
// The table itself goes in the data segment. First comes the X
// and Y origins. // and Y origins.
emitDataWord(xRange ? xRange[0] : 0) out << "byte[] triggerTbl = ${xRange ? xRange[0] : 0}, ${yRange ? yRange[0] : 0} // origin X,Y\n"
emitDataWord(yRange ? yRange[0] : 0)
// Then the Y tables // Then the Y tables
triggers.each { y, xs -> triggers.each { y, xs ->
emitDataByte(y)
def size = 2 // 2 bytes for y+off def size = 2 // 2 bytes for y+off
xs.each { x, funcAddrs -> xs.each { x, funcs ->
size += funcAddrs.size() * 3 // plus 3 bytes per trigger (x, adrlo, adrhi) size += funcs.size() * 3 // plus 3 bytes per trigger (x, adrlo, adrhi)
} }
emitDataByte(size) out << "byte = $y, $size // Y=$y, size=$size\n"
xs.each { x, funcAddrs -> xs.each { x, funcs ->
funcAddrs.each { funcAddr -> funcs.each { func ->
emitDataByte(x) out << " byte = $x; word = @$func // X=$x\n"
emitDataFixup(funcAddr)
} }
// Record a list of trigger locations for the caller's reference // Record a list of trigger locations for the caller's reference
locationsWithTriggers << [x, y] locationsWithTriggers << [x, y]
} }
} }
emitDataByte(0xFF) // mark the end end of the trigger table out << "byte = \$FF\n\n"
}
def makeInit(mapName, scripts, maxX, maxY)
{
// Emit the code the user has stored for the init script.
scripts.each { script ->
if (script.block.size() == 1)
packBlock(getSingle(getSingle(script.block, null, 'procedures_defreturn').statement, 'STACK'))
}
// Code to register the map name, trigger table, and map extent.
def shortName = mapName.replaceAll(/[\s-]*[23][dD][-0-9]*$/, '').take(16)
out << "setScriptInfo(\"$shortName\", triggerTbl, $maxX, $maxY)\n"
// All done with the init function. // All done with the init function.
finishFunc() out << "done\n"
}
def packFixups()
{
def buf = []
fixups.each { fromOff, fromType, toOff, toType ->
assert fromType == 'bytecode'
assert toType == 'data'
toOff += (nScripts+1) * 5
}
} }
} }