diff --git a/build.gradle b/build.gradle index 5f4a317..f0211fe 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.jetbrains.kotlin.multiplatform' version '1.3.72' + id 'org.jetbrains.kotlin.multiplatform' version '1.3.70' id 'com.github.johnrengelman.shadow' version '5.0.0' } diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/BotCommand.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/BotCommand.kt index 5efd09d..ffd37d7 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/BotCommand.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/BotCommand.kt @@ -121,7 +121,7 @@ enum class BotCommand( val addsTo = when(returnType) { RETURNVAL_INT, RETURNVAL_BOOLEAN -> ADDS_TO_NORMAL_STACK RETURNVAL_STRING -> ADDS_TO_STRING_STACK - RETURNVAL_VOID -> DONT_PUSHES_TO_STACK + RETURNVAL_VOID -> DOES_NOT_PUSH_TO_STACK } return FunctionNode(readableName, args, addsTo) diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt index 28f46a0..bdde726 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt @@ -4,9 +4,21 @@ import com.github.tarcv.zandronum.debotc.StackChangingNode.AddsTo.* import com.github.tarcv.zandronum.debotc.StackChangingNode.ArgumentHolder.Companion.createNormalStackArgument import com.github.tarcv.zandronum.debotc.StackChangingNode.Companion.consumesNormalStack +@ExperimentalUnsignedTypes enum class DataHeaders(requiredArgs: Int = 0) { DH_COMMAND(2) { + override fun parseCommand(parseState: ParseState): Command? { + val positionBefore = parseState.data.offset - 4 + val command = parseState.data.readSigned32() + if (command < 0 || command >= BotCommand.NUM_BOTCMDS.ordinal) { + throw IllegalStateException("Illegal command $command at offset $positionBefore") + } + val argument = parseState.data.readSigned32() + + return Command(positionBefore, parseState.data.offset, DH_COMMAND, command, argument) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { val botCommand: BotCommand = toEnum(command.arguments[0]) val expectedSecondArg = botCommand.numArgs + botCommand.numStringArgs @@ -35,6 +47,10 @@ enum class DataHeaders(requiredArgs: Int = 0) }, DH_EVENT { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode = throw IllegalStateException() + + override fun describeReadableForDisasm(command: Command): String { + throw IllegalStateException("Should not happen") + } }, DH_ENDONENTER { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode = throw IllegalStateException() @@ -47,151 +63,261 @@ enum class DataHeaders(requiredArgs: Int = 0) }, DH_ENDEVENT { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode = throw IllegalStateException() + + override fun describeReadableForDisasm(command: Command): String = "}" }, DH_IFGOTO(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1, isGoto = true) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return IfGotoNode(createNormalStackArgument(0), command.arguments[0]) } }, DH_IFNOTGOTO(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1, isGoto = true) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return IfNotGotoNode(createNormalStackArgument(0), command.arguments[0]) } }, DH_GOTO(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1, isGoto = true) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return GotoNode(command.arguments[0]) } }, DH_ORLOGICAL { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("||", 2) } }, DH_ANDLOGICAL { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("&&", 2) } }, DH_ORBITWISE { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("|", 2) } }, DH_EORBITWISE { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("^", 2) } }, DH_ANDBITWISE { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("&", 2) } }, DH_EQUALS { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("==", 2) } }, DH_NOTEQUALS { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("!=", 2) } }, DH_LESSTHAN { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("<", 2) } }, DH_LESSTHANEQUALS { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("<=", 2) } }, DH_GREATERTHAN { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode(">", 2) } }, DH_GREATERTHANEQUALS { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode(">=", 2) } }, DH_NEGATELOGICAL { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("!", 1) } }, DH_LSHIFT { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("<<", 2) } }, DH_RSHIFT { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode(">>", 2) } }, DH_ADD { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("+", 2) } }, DH_SUBTRACT { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("-", 2) } }, DH_UNARYMINUS { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("-", 1) } }, DH_MULTIPLY { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("*", 2) } }, DH_DIVIDE { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("/", 2) } }, DH_MODULUS { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("%", 2) } }, DH_PUSHNUMBER { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return LiteralNode(command.arguments[0].toString(), ADDS_TO_NORMAL_STACK) } }, DH_PUSHSTRINGINDEX { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return LiteralNode("\"${vmState.strings[command.arguments[0]]}\"", ADDS_TO_STRING_STACK) - + } + + override fun describeReadableForDisasm(command: Command): String { + val cmd = command as Command + return "stringStack.push(strings[${cmd.arguments[0]}])" } }, DH_PUSHGLOBALVAR { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) return LiteralNode("\$global${command.arguments[0]}", ADDS_TO_NORMAL_STACK) @@ -199,6 +325,10 @@ enum class DataHeaders(requiredArgs: Int = 0) } }, DH_PUSHLOCALVAR { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) return LiteralNode("\$local${command.arguments[0]}", ADDS_TO_NORMAL_STACK) @@ -206,6 +336,10 @@ enum class DataHeaders(requiredArgs: Int = 0) } }, DH_DROPSTACKPOSITION { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return dropFromStack() } @@ -221,102 +355,174 @@ enum class DataHeaders(requiredArgs: Int = 0) } }, DH_INCGLOBALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) return CommandNode("\$global${command.arguments[0]}++;") } }, DH_DECGLOBALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) return CommandNode("\$global${command.arguments[0]}--;") } }, DH_ASSIGNGLOBALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} = ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} = ${stackArgs[0]};" } } }, DH_ADDGLOBALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} += ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} += ${stackArgs[0]};" } } }, DH_SUBGLOBALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} -= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} -= ${stackArgs[0]};" } } }, DH_MULGLOBALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} *= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} *= ${stackArgs[0]};" } } }, DH_DIVGLOBALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} /= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} /= ${stackArgs[0]};" } } }, DH_MODGLOBALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} %= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} %= ${stackArgs[0]};" } } }, DH_INCLOCALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(0, DONT_PUSHES_TO_STACK) { "\$local${command.arguments[0]}++;" } + return CustomStackConsumingNode(0, DOES_NOT_PUSH_TO_STACK) { "\$local${command.arguments[0]}++;" } } }, DH_DECLOCALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(0, DONT_PUSHES_TO_STACK) { "\$local${command.arguments[0]}--;" } + return CustomStackConsumingNode(0, DOES_NOT_PUSH_TO_STACK) { "\$local${command.arguments[0]}--;" } } }, DH_ASSIGNLOCALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} = ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} = ${stackArgs[0]};" } } }, DH_ADDLOCALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} += ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} += ${stackArgs[0]};" } } }, DH_SUBLOCALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} -= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} -= ${stackArgs[0]};" } } }, DH_MULLOCALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} *= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} *= ${stackArgs[0]};" } } }, DH_DIVLOCALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} /= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} /= ${stackArgs[0]};" } } }, DH_MODLOCALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} %= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} %= ${stackArgs[0]};" } } }, DH_CASEGOTO(2) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 2, isGoto = true) + } + + override fun describeReadableForDisasm(command: Command): String { + return "if (stack[0] == ${command.arguments[0]}) goto label${String.format("%X", command.arguments[1])}" + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return if (vmState.previousCommand != DH_CASEGOTO) { SwitchAndCaseNode(createNormalStackArgument(0), command.arguments[0].toString(), command.arguments[1]) @@ -326,68 +532,108 @@ enum class DataHeaders(requiredArgs: Int = 0) } }, DH_DROP { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return dropFromStack() } }, DH_INCGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}]++;" } } }, DH_DECGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}]--;" } } }, DH_ASSIGNGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DOES_NOT_PUSH_TO_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] = ${stackArgs[1]};" } } }, DH_ADDGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DOES_NOT_PUSH_TO_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] += ${stackArgs[1]};" } } }, DH_SUBGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DOES_NOT_PUSH_TO_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] -= ${stackArgs[1]};" } } }, DH_MULGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DOES_NOT_PUSH_TO_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] *= ${stackArgs[1]};" } } }, DH_DIVGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DOES_NOT_PUSH_TO_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] /= ${stackArgs[1]};" } } }, DH_MODGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DOES_NOT_PUSH_TO_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] %= ${stackArgs[1]};" } } }, DH_PUSHGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return CustomStackConsumingNode(1, ADDS_TO_NORMAL_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) @@ -395,6 +641,10 @@ enum class DataHeaders(requiredArgs: Int = 0) } }, DH_SWAP{ + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return object : StackChangingNode( consumesNormalStack(2), @@ -424,20 +674,77 @@ enum class DataHeaders(requiredArgs: Int = 0) } }, DH_ARRAYSET{ + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return FunctionNode("memset", consumesNormalStack(3), DONT_PUSHES_TO_STACK) + return FunctionNode("memset", consumesNormalStack(3), DOES_NOT_PUSH_TO_STACK) } }, NUM_DATAHEADERS { + override fun parseCommand(parseState: ParseState): Command? { + throw IllegalStateException("Must not be called") + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { throw IllegalStateException("Must not be called") } }; abstract fun processAndCreateNode(command: Command, vmState: VmState): BaseNode + + open fun parseCommand(parseState: ParseState): Command? = null + + open fun describeReadableForDisasm(command: Command): String { + var text = processAndCreateNode(command, VmState(emptyList(), HashSet(), HashSet(), HashSet())).asText + while (text.endsWith(";")) { + text = text.removeSuffix(";").trim() + } + while( text.startsWith("(") && text.endsWith(")")) { + text = text.removePrefix("(").removeSuffix(")").trim() + } + if (text.startsWith("stack = ")) { + text = "stack.push( ${text.removePrefix("stack = ")} )" + } + if (text.contains("stack[") || text.contains("stringStack")) { + text += " // stack arguments are popped by the function" + } + text = text.replace(Regex("(?<=[\\s|^])goto label(?=[\\dA-F])"), "goto 0x") + return text + } + + fun describeForDisasm(command: Command?): DisasmDescription { + if (command != null) { + return DisasmDescription( + command.arguments.joinToString(", ") ?: "", + describeReadableForDisasm(command) + ) + } else { + throw IllegalStateException("Should not be called for non commands") + } + } } +class DisasmDescription( + val arguments: String, + val readable: String +) + private fun dropFromStack(): BaseNode { return DropStackNode("// item dropped from stack") } + +@ExperimentalUnsignedTypes +private fun parseCommandWithArg(parseState: ParseState, commandHeader: DataHeaders, numberArguments: Int, isGoto: Boolean = false): Command { + val positionBefore = parseState.data.offset - 4 + val args = IntArray(numberArguments) + for (i in 0 until numberArguments) { + args[i] = parseState.data.readSigned32() + } + if (isGoto) { + parseState.labelPositions.add(args.last()) + } + return Command(positionBefore, parseState.data.offset, commandHeader, *args) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt index e01f023..822abee 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt @@ -1,6 +1,5 @@ package com.github.tarcv.zandronum.debotc -import com.github.tarcv.zandronum.debotc.BotCommand.NUM_BOTCMDS import com.github.tarcv.zandronum.debotc.DataHeaders.* import com.github.tarcv.zandronum.debotc.LiteralNode.Companion.consumedMarker import com.github.tarcv.zandronum.debotc.StackChangingNode.AddsTo.* @@ -11,14 +10,15 @@ import kotlin.math.max @ExperimentalUnsignedTypes class Decompiler { - fun print() { + + fun print(printer: Printer) { if (!alreadyParsed) { throw IllegalStateException("print() can be called only after parsing") } - println("#!botc 1.0.0") - println("#include \"debotc_defs.bts\"") - println() + printer.println("#!botc 1.0.0") + printer.println("#include \"debotc_defs.bts\"") + printer.println() val globalVariables = HashSet() val globalArrays = HashSet() @@ -57,12 +57,12 @@ class Decompiler { stateBuilder.appendLine() } .let { - printBlockForGlobal(globalVariables, globalArrays) - println() + printBlockForGlobal(printer, globalVariables, globalArrays) + printer.println() it } .forEach { - println(it) + printer.println(it.toString()) } } @@ -76,16 +76,16 @@ class Decompiler { .appendLine() } - private fun printBlockForGlobal(globalVariables: Set, globalArrays: Set) { + private fun printBlockForGlobal(printer: Printer, globalVariables: Set, globalArrays: Set) { globalVariables .sorted() .forEach { - println("var int \$global$it;") + printer.println("var int \$global$it;") } globalArrays .sorted() .forEach { - println("var int \$globalArray$it[];") + printer.println("var int \$globalArray$it[];") } } @@ -312,7 +312,7 @@ class Decompiler { gotos.forEach { val targetByte = it.targetByte - it.jumpTargetNode = labels[targetByte] ?: throw IllegalStateException("Label$targetByte not found") + it.jumpTargetNode = labels[targetByte] ?: throw IllegalStateException("Label${String.format("%X", targetByte)} not found") if (it is GotoNode) { replaceGotoWithEdge(it) @@ -334,7 +334,7 @@ class Decompiler { } } - fun parse(data0: UByteArray) { + fun parse(data0: UByteArray, disasmPrinter: Printer?) { if (alreadyParsed) { throw IllegalStateException("parse() can be called only once") } @@ -342,7 +342,7 @@ class Decompiler { data = Data(data0) - parseCommands() + parseCommands(disasmPrinter) normalizeStates() } @@ -356,51 +356,65 @@ class Decompiler { } } - private fun parseCommands() { + private fun parseCommands(disasmDumper: Printer? = null) { + val parseState = ParseState(data, labelPositions) while (data.offset < data.data.size) { + val offset = data.offset val index = data.readSigned32() val commandHeader: DataHeaders = toEnum(index) - when (commandHeader) { - DH_COMMAND -> parseBotCommand(data) - DH_ONENTER, DH_MAINLOOP, DH_ONEXIT -> parseEventHandler(commandHeader) - DH_EVENT -> parseEvent(data) - DH_ENDONENTER, DH_ENDMAINLOOP, DH_ENDONEXIT, DH_ENDEVENT -> finalizeEvent() - DH_STATENAME -> parseStateName(data) - DH_STATEIDX -> throw IllegalStateException("DH_STATEIDX without DH_STATENAME is not supported") - DH_PUSHNUMBER, DH_PUSHSTRINGINDEX, DH_PUSHGLOBALVAR, DH_PUSHLOCALVAR, DH_INCGLOBALVAR, DH_DECGLOBALVAR, - DH_ASSIGNGLOBALVAR, DH_ADDGLOBALVAR, DH_SUBGLOBALVAR, DH_MULGLOBALVAR, - DH_DIVGLOBALVAR, DH_MODGLOBALVAR, DH_INCLOCALVAR, DH_DECLOCALVAR, DH_ASSIGNLOCALVAR, - DH_ADDLOCALVAR, DH_SUBLOCALVAR, DH_MULLOCALVAR, DH_DIVLOCALVAR, DH_MODLOCALVAR, - DH_INCGLOBALARRAY, DH_DECGLOBALARRAY, DH_ASSIGNGLOBALARRAY, DH_ADDGLOBALARRAY, - DH_SUBGLOBALARRAY, DH_MULGLOBALARRAY, DH_DIVGLOBALARRAY, DH_MODGLOBALARRAY, - DH_PUSHGLOBALARRAY - -> parseCommandWithArg(commandHeader, 1) - DH_IFGOTO, DH_IFNOTGOTO, DH_GOTO - -> parseCommandWithArg(commandHeader, 1, isGoto = true) - DH_SCRIPTVARLIST -> parseScriptVarList() - DH_CASEGOTO -> parseCommandWithArg(commandHeader, 2, isGoto = true) - DH_ORLOGICAL, DH_ANDLOGICAL, DH_ORBITWISE, DH_EORBITWISE, DH_ANDBITWISE, - DH_EQUALS, DH_NOTEQUALS, DH_DROPSTACKPOSITION, DH_LESSTHAN, DH_LESSTHANEQUALS, - DH_GREATERTHAN, DH_GREATERTHANEQUALS, DH_NEGATELOGICAL, DH_LSHIFT, DH_RSHIFT, - DH_ADD, DH_SUBTRACT, DH_UNARYMINUS, DH_MULTIPLY, DH_DIVIDE, DH_MODULUS, DH_DROP, - DH_SWAP, DH_ARRAYSET - -> parseCommandWithArg(commandHeader, 0) - DH_STRINGLIST -> addStrings() - else -> throw IllegalStateException("Unexpected header $commandHeader at ${data.offset}") + + if (disasmDumper != null) { + disasmDumper.println() + disasmDumper.print("${String.format("%08X", offset)} | ${commandHeader.name} ") + } + + val command = commandHeader.parseCommand(parseState) + + val disasmDescription: DisasmDescription + if (command == null) { + disasmDescription = when (commandHeader) { + DH_ONENTER, DH_MAINLOOP, DH_ONEXIT -> parseEventHandler(commandHeader) + DH_EVENT -> parseEvent(data) + DH_ENDONENTER, DH_ENDMAINLOOP, DH_ENDONEXIT, DH_ENDEVENT -> finalizeEvent() + DH_STATENAME -> parseStateName(data) + DH_STATEIDX -> throw IllegalStateException("DH_STATEIDX without DH_STATENAME is not supported") + DH_SCRIPTVARLIST -> parseScriptVarList() + DH_STRINGLIST -> addStrings() + + else -> throw IllegalStateException("Unexpected header $commandHeader at ${data.offset}") + } + } else { + disasmDescription = commandHeader.describeForDisasm(command) + currentState.currentEvent.addCommand(command) + } + + if (disasmDumper != null) { + disasmDumper.print(disasmDescription.arguments) + disasmDumper.print(" ".repeat(max(0, 30 - disasmDescription.arguments.length - commandHeader.name.length))) + disasmDumper.print(" | ${disasmDescription.readable}") } } } - private fun parseScriptVarList() { - currentState.currentEvent.varList = data.readSigned32() + private fun parseScriptVarList(): DisasmDescription { + val varList = data.readSigned32() + currentState.currentEvent.varList = varList + return DisasmDescription( + varList.toString(), + "// define $varList script variables" + ) } - private fun finalizeEvent() { + private fun finalizeEvent(): DisasmDescription { eventEnds.add((data.offset - 4).toString()) currentState.currentEvent.finalize() + return DisasmDescription( + "", + "}" + ) } - private fun addStrings() { + private fun addStrings(): DisasmDescription { val numStrings = data.readSigned32() if (strings.isNotEmpty()) { throw IllegalStateException("Strings were already added") @@ -410,21 +424,15 @@ class Decompiler { val str = data.readSzString(length) strings.add(str) } - } - private fun parseCommandWithArg(commandHeader: DataHeaders, numberArguments: Int, isGoto: Boolean = false) { - val positionBefore = data.offset - 4 - val args = IntArray(numberArguments) - for (i in 0 until numberArguments) { - args[i] = data.readSigned32() - } - if (isGoto) { - labelPositions.add(args.last()) - } - currentState.currentEvent.addCommand(Command(positionBefore, data.offset, commandHeader, *args)) + val joinedStrings = strings.joinToString(", ") { "\"$it\"" } + return DisasmDescription( + "$numStrings, $joinedStrings", + "strings = {$joinedStrings}" + ) } - private fun parseStateName(data: Data) { + private fun parseStateName(data: Data): DisasmDescription { val size = data.readSigned32() val name = data.readSzString(size) val stateIndexHeader = data.readSigned32() @@ -437,20 +445,16 @@ class Decompiler { throw IllegalStateException("Illegal stateIndex: $stateIndex") } - states.add(com.github.tarcv.zandronum.debotc.State(name, stateIndex)) - } + val state = State(name, stateIndex) + states.add(state) - private fun parseBotCommand(data: Data) { - val positionBefore = data.offset - 4 - val command = data.readSigned32() - if (command < 0 || command >= NUM_BOTCMDS.ordinal) { - throw IllegalStateException("Illegal command $command in ${currentState.name}") - } - val argument = data.readSigned32() - currentState.currentEvent.addCommand(Command(positionBefore, data.offset, DH_COMMAND, command, argument)) + return DisasmDescription( + "\"$name\" (DH_STATEIDX $stateIndex)", + "state ${state.name} {" + ) } - private fun parseEvent(data: Data) { + private fun parseEvent(data: Data): DisasmDescription { val eventType: BotEventType = toEnum(data.readSigned32()) if (currentState.global) { if (currentState.events.size >= MAX_NUM_GLOBAL_EVENTS) { @@ -461,15 +465,27 @@ class Decompiler { throw IllegalStateException("Too many local events in the bot script") } } - currentState.events.add(BotEvent(eventType)) + val event = BotEvent(eventType) + currentState.events.add(event) + + return DisasmDescription( + eventType.name, + "${event.botcTitle} {" + ) } - private fun parseEventHandler(event: DataHeaders) { + private fun parseEventHandler(event: DataHeaders): DisasmDescription { if (currentState.global) { throw IllegalStateException("$event outside of a state definition") } - currentState.events.add(WorldEvent(event)) + val event = WorldEvent(event) + currentState.events.add(event) + + return DisasmDescription( + "", + event.botcTitle + " {" + ) } private var alreadyParsed: Boolean = false @@ -542,7 +558,7 @@ class Data constructor( private set } -val changingAddTos = StackChangingNode.AddsTo.values().filter { it != DONT_PUSHES_TO_STACK } +val changingAddTos = StackChangingNode.AddsTo.values().filter { it != DOES_NOT_PUSH_TO_STACK } fun inlineStackArgs(node: BaseNode): Boolean { var changed = false @@ -623,7 +639,7 @@ private fun tryLiteralizeNextNode(node: BaseNode): Boolean { assert(BotCommand.BOTCMD_DELAY.returnType == BotCommandReturnType.RETURNVAL_VOID) val returns = nextNode.returns() - val replacingNode = when (returns.count { it.addsTo != DONT_PUSHES_TO_STACK }) { + val replacingNode = when (returns.count { it.addsTo != DOES_NOT_PUSH_TO_STACK }) { 0 -> { if (returns.size != 1) throw AssertionError() CommandNode(returns[0].addsTo.asText() + returns[0].value) @@ -964,5 +980,11 @@ fun packSwitchBlockToText(node: BaseNode): Boolean { return changed } +@ExperimentalUnsignedTypes +class ParseState( + val data: Data, + val labelPositions: HashSet +) + private fun String.indent() = this.replace(Regex("^", MULTILINE), "\t") \ No newline at end of file diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt new file mode 100644 index 0000000..378b75b --- /dev/null +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt @@ -0,0 +1,37 @@ +package com.github.tarcv.zandronum.debotc + +@ExperimentalUnsignedTypes +fun main(rawArgs: Array) { + val args = rawArgs.toMutableList() + + val disasmMode = if (args.isNotEmpty() && args[0] == "--disasm") { + args.removeAt(0) + true + } else { + false + } + if (args.size != 1 && args.size != 2) { + throw IllegalArgumentException("Wrong arguments${lineSeparator}debotc [--disasm] []") + } + + val data0: UByteArray = readAllBytes(args[0]) + + val printer = if (args.size == 2) { + FilePrinter(args[1]) + } else { + consolePrinter + } + + try { + val decompiler = Decompiler() + + if (disasmMode) { + decompiler.parse(data0, printer) + } else { + decompiler.parse(data0, null) + decompiler.print(printer) + } + } finally { + printer.close() + } +} diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt index a943dc3..9691319 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt @@ -163,7 +163,7 @@ abstract class StackChangingNode( ) : BaseNode("", outputNum), ConsumesStack { enum class AddsTo { - DONT_PUSHES_TO_STACK { + DOES_NOT_PUSH_TO_STACK { override fun asText(): String = "" }, ADDS_TO_NORMAL_STACK { @@ -313,16 +313,16 @@ class LiteralNode(pairs: List>) class DropStackNode(override val asText: String) : StackChangingNode( listOf(createNormalStackArgument(0)), - arrayOf(ReturnPrototype(DONT_PUSHES_TO_STACK, { arguments -> "// dropped '${arguments[0]}'" })) + arrayOf(ReturnPrototype(DOES_NOT_PUSH_TO_STACK, { arguments -> "// dropped '${arguments[0]}'" })) ) -open class LabelNode(byte: Int) : BaseNode("label$byte", 1) +open class LabelNode(byte: Int) : BaseNode("label${String.format("%X", byte)}", 1) interface JumpingNode { var jumpTargetNode: BaseNode } -open class AbstractGotoNode(val targetByte: Int) : BaseNode("goto label$targetByte", 2), JumpingNode { +open class AbstractGotoNode(val targetByte: Int) : BaseNode("goto label${String.format("%X", targetByte)}", 2), JumpingNode { override var jumpTargetNode: BaseNode get() = outputs[1] set(value) { diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Util.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Util.kt index af999dd..d020c0b 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Util.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Util.kt @@ -13,4 +13,18 @@ expect fun StringBuilder.appendLine(line: StringBuilder): StringBuilder expect fun StringBuilder.insertString(index: Int, string: String): StringBuilder -expect fun ByteArray.stringFromUtf8BytesOrThrow(start: Int = 0, size: Int = this.size): String \ No newline at end of file +expect fun String.Companion.format(format: String, arg: Int): String + +expect fun ByteArray.stringFromUtf8BytesOrThrow(start: Int = 0, size: Int = this.size): String + +@ExperimentalUnsignedTypes +expect fun readAllBytes(path: String): UByteArray + +expect interface Printer { + fun println(msg: String = "") + + fun close() + fun print(msg: String) +} +expect class FilePrinter(path: String): Printer +expect object consolePrinter: Printer \ No newline at end of file diff --git a/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt b/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt deleted file mode 100644 index 915776f..0000000 --- a/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.github.tarcv.zandronum.debotc - -import java.nio.file.Files -import java.nio.file.Paths - -@ExperimentalUnsignedTypes -fun main(args: Array) { - if (args.size != 1) { - throw IllegalArgumentException("Only one argument - path to compiled script should be passed") - } - - val data = Files.readAllBytes(Paths.get(args[0])) - val data0 = data.toUByteArray() - - val decompiler = Decompiler() - decompiler.parse(data0) - decompiler.print() -} \ No newline at end of file diff --git a/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsJvm.kt b/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsJvm.kt index 5a83698..8a722b4 100644 --- a/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsJvm.kt +++ b/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsJvm.kt @@ -1,12 +1,20 @@ package com.github.tarcv.zandronum.debotc +import java.io.Closeable +import java.io.File import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Paths /** * JVM implementations of platform-dependent helpers */ actual val lineSeparator: String = System.lineSeparator() +actual fun String.Companion.format(format: String, arg: Int): String { + return String.format(format, *arrayOf(arg)) +} + actual fun StringBuilder.appendLine(): StringBuilder { return this.appendln() } @@ -29,4 +37,45 @@ actual fun StringBuilder.insertString(index: Int, string: String): StringBuilder actual fun assert(value: Boolean) { kotlin.assert(value) -} \ No newline at end of file +} + +actual interface Printer: Closeable { + actual fun println(msg: String) + actual override fun close() + actual fun print(msg: String) +} + +actual class FilePrinter actual constructor(path: String) : Printer { + private val writer = File(path).printWriter() + + override fun close() { + writer.close() + } + + override fun print(msg: String) { + writer.print(msg) + } + + override fun println(msg: String) { + writer.println(msg) + } +} + +actual object consolePrinter: Printer { + override fun close() { + // no op + } + + override fun print(msg: String) { + kotlin.io.print(msg) + } + + override fun println(msg: String) { + kotlin.io.println(msg) + } +} + +@ExperimentalUnsignedTypes +actual fun readAllBytes(path: String): UByteArray { + return Files.readAllBytes(Paths.get(path)).toUByteArray() +} diff --git a/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt b/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt index bcc62e3..d4326d4 100644 --- a/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt +++ b/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt @@ -12,7 +12,7 @@ class CounterexamplesTest { val rootNode = BeginNode() rootNode .attachNode(FunctionNode("IsEnemyVisible", emptyList(), StackChangingNode.AddsTo.ADDS_TO_NORMAL_STACK)) - .attachNode(FunctionNode("ClearEnemy", emptyList(), StackChangingNode.AddsTo.DONT_PUSHES_TO_STACK)) + .attachNode(FunctionNode("ClearEnemy", emptyList(), StackChangingNode.AddsTo.DOES_NOT_PUSH_TO_STACK)) .attachNode(FunctionNode("Function", StackChangingNode.consumesNormalStack(1), StackChangingNode.AddsTo.ADDS_TO_NORMAL_STACK)) .attachNode(EndNode()) return rootNode diff --git a/src/nativeMain/kotlin/Main.kt b/src/nativeMain/kotlin/Main.kt deleted file mode 100644 index 5e94eec..0000000 --- a/src/nativeMain/kotlin/Main.kt +++ /dev/null @@ -1,31 +0,0 @@ -import kotlinx.cinterop.* -import platform.posix.* -import com.github.tarcv.zandronum.debotc.Decompiler - -fun main(args: Array) { - if (args.size != 1) { - throw IllegalArgumentException("Only one argument - path to compiled script should be passed") - } - var data0 = UByteArray(0) - - val file = fopen(args[0], "rb") - try { - fseek(file, 0, SEEK_END) - val size = ftell(file).toInt(); - rewind(file) - memScoped { - val buffer = allocArray(size+1) - val elementsRead = fread(buffer, size.convert(), 1, file).toInt() - if (elementsRead != 1) { - throw IllegalStateException("Failed to read file") - } - data0 = buffer.readBytes(size).toUByteArray() - } - } finally { - fclose(file) - } - - val decompiler = Decompiler() - decompiler.parse(data0) - decompiler.print() -} \ No newline at end of file diff --git a/src/nativeMain/kotlin/MainNative.kt b/src/nativeMain/kotlin/MainNative.kt new file mode 100644 index 0000000..2d5b85a --- /dev/null +++ b/src/nativeMain/kotlin/MainNative.kt @@ -0,0 +1,3 @@ +fun main(args: Array) { + com.github.tarcv.zandronum.debotc.main(args) +} \ No newline at end of file diff --git a/src/nativeMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsNative.kt b/src/nativeMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsNative.kt index 299c43d..fe5c984 100644 --- a/src/nativeMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsNative.kt +++ b/src/nativeMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsNative.kt @@ -1,8 +1,21 @@ package com.github.tarcv.zandronum.debotc +import platform.posix.* +import kotlinx.cinterop.* + /** * Native implementations of platform-dependent helpers */ + +actual fun String.Companion.format(format: String, arg: Int): String { + return memScoped { + val size = snprintf(null, 0, format, arg) + 1 + val buffer = allocArray(size) + snprintf(buffer, size.toULong(), format, arg) + buffer.toKString() + } +} + actual fun StringBuilder.appendLine(): StringBuilder { return this.appendln() } @@ -27,4 +40,63 @@ actual val lineSeparator: String = "\n" // TODO: get system line separator actual fun assert(value: Boolean) { kotlin.assert(value) +} + +actual interface Printer { + actual fun println(msg: String) + actual fun close() + actual fun print(msg: String) +} + +actual class FilePrinter actual constructor(path: String) : Printer { + private val file = fopen(path, "a") + + override fun close() { + fclose(file) + } + + override fun print(msg: String) { + fputs(msg, file) + } + + override fun println(msg: String) { + fputs(msg, file) + fputs(lineSeparator, file) + } +} + +actual object consolePrinter: Printer { + override fun close() { + // no op + } + + override fun print(msg: String) { + kotlin.io.print(msg) + } + + override fun println(msg: String) { + kotlin.io.println(msg) + } +} + +actual fun readAllBytes(path: String): UByteArray { + val file = fopen(path, "rb") ?: throw IllegalStateException("Failed to read file") + + try { + fseek(file, 0, SEEK_END) + val size = ftell(file).toInt(); + rewind(file) + return memScoped { + val buffer = allocArray(size + 1) + val elementsRead = fread(buffer, size.convert(), 1, file).toInt() + if (elementsRead != 1) { + throw IllegalStateException("Failed to read file") + } + + buffer.readBytes(size).toUByteArray() + } + } finally { + fclose(file) + } + } \ No newline at end of file