mirror of
https://github.com/irmen/prog8.git
synced 2024-06-13 09:29:34 +00:00
2118 lines
94 KiB
Kotlin
2118 lines
94 KiB
Kotlin
package prog8.compiler
|
|
|
|
import prog8.ast.*
|
|
import prog8.ast.base.*
|
|
import prog8.ast.base.RegisterOrPair.*
|
|
import prog8.ast.expressions.*
|
|
import prog8.ast.processing.IAstProcessor
|
|
import prog8.ast.statements.*
|
|
import prog8.compiler.intermediate.IntermediateProgram
|
|
import prog8.compiler.intermediate.Opcode
|
|
import prog8.compiler.intermediate.branchOpcodes
|
|
import prog8.functions.BuiltinFunctions
|
|
import prog8.parser.tryGetEmbeddedResource
|
|
import prog8.stackvm.Syscall
|
|
import java.io.File
|
|
import java.nio.file.Path
|
|
import java.util.*
|
|
import kotlin.math.abs
|
|
|
|
|
|
class CompilerException(message: String?) : Exception(message)
|
|
|
|
|
|
fun Number.toHex(): String {
|
|
// 0..15 -> "0".."15"
|
|
// 16..255 -> "$10".."$ff"
|
|
// 256..65536 -> "$0100".."$ffff"
|
|
// negative values are prefixed with '-'.
|
|
val integer = this.toInt()
|
|
if(integer<0)
|
|
return '-' + abs(integer).toHex()
|
|
return when (integer) {
|
|
in 0 until 16 -> integer.toString()
|
|
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0')
|
|
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0')
|
|
else -> throw CompilerException("number too large for 16 bits $this")
|
|
}
|
|
}
|
|
|
|
|
|
data class IntegerOrAddressOf(val integer: Int?, val addressOf: AddressOf?)
|
|
|
|
|
|
class HeapValues {
|
|
data class HeapValue(val type: DataType, val str: String?, val array: Array<IntegerOrAddressOf>?, val doubleArray: DoubleArray?) {
|
|
override fun equals(other: Any?): Boolean {
|
|
if (this === other) return true
|
|
if (javaClass != other?.javaClass) return false
|
|
other as HeapValue
|
|
return type==other.type && str==other.str && Arrays.equals(array, other.array) && Arrays.equals(doubleArray, other.doubleArray)
|
|
}
|
|
|
|
override fun hashCode(): Int {
|
|
var result = type.hashCode()
|
|
result = 31 * result + (str?.hashCode() ?: 0)
|
|
result = 31 * result + (array?.let { Arrays.hashCode(it) } ?: 0)
|
|
result = 31 * result + (doubleArray?.let { Arrays.hashCode(it) } ?: 0)
|
|
return result
|
|
}
|
|
|
|
val arraysize: Int = array?.size ?: doubleArray?.size ?: 0
|
|
}
|
|
|
|
private val heap = mutableMapOf<Int, HeapValue>()
|
|
private var heapId = 1
|
|
|
|
fun size(): Int = heap.size
|
|
|
|
fun addString(type: DataType, str: String): Int {
|
|
if (str.length > 255)
|
|
throw IllegalArgumentException("string length must be 0-255")
|
|
|
|
// strings are 'interned' and shared if they're the isSameAs
|
|
val value = HeapValue(type, str, null, null)
|
|
|
|
val existing = heap.filter { it.value==value }.map { it.key }.firstOrNull()
|
|
if(existing!=null)
|
|
return existing
|
|
val newId = heapId++
|
|
heap[newId] = value
|
|
return newId
|
|
}
|
|
|
|
fun addIntegerArray(type: DataType, array: Array<IntegerOrAddressOf>): Int {
|
|
// arrays are never shared, don't check for existing
|
|
if(type !in ArrayDatatypes)
|
|
throw CompilerException("wrong array type")
|
|
val newId = heapId++
|
|
heap[newId] = HeapValue(type, null, array, null)
|
|
return newId
|
|
}
|
|
|
|
fun addDoublesArray(darray: DoubleArray): Int {
|
|
// arrays are never shared, don't check for existing
|
|
val newId = heapId++
|
|
heap[newId] = HeapValue(DataType.ARRAY_F, null, null, darray)
|
|
return newId
|
|
}
|
|
|
|
fun update(heapId: Int, str: String) {
|
|
val oldVal = heap[heapId] ?: throw IllegalArgumentException("heapId not found in heap")
|
|
if(oldVal.type in StringDatatypes) {
|
|
if (oldVal.str!!.length != str.length)
|
|
throw IllegalArgumentException("heap string length mismatch")
|
|
heap[heapId] = oldVal.copy(str = str)
|
|
}
|
|
else throw IllegalArgumentException("heap data type mismatch")
|
|
}
|
|
|
|
fun update(heapId: Int, heapval: HeapValue) {
|
|
if(heapId !in heap)
|
|
throw IllegalArgumentException("heapId not found in heap")
|
|
heap[heapId] = heapval
|
|
}
|
|
|
|
fun get(heapId: Int): HeapValue {
|
|
return heap[heapId] ?:
|
|
throw IllegalArgumentException("heapId not found in heap")
|
|
}
|
|
|
|
fun allEntries() = heap.entries
|
|
}
|
|
|
|
|
|
enum class OutputType {
|
|
RAW,
|
|
PRG
|
|
}
|
|
|
|
enum class LauncherType {
|
|
BASIC,
|
|
NONE
|
|
}
|
|
|
|
enum class ZeropageType {
|
|
BASICSAFE,
|
|
FLOATSAFE,
|
|
KERNALSAFE,
|
|
FULL
|
|
}
|
|
|
|
|
|
data class CompilationOptions(val output: OutputType,
|
|
val launcher: LauncherType,
|
|
val zeropage: ZeropageType,
|
|
val zpReserved: List<IntRange>,
|
|
val floats: Boolean)
|
|
|
|
|
|
internal class Compiler(private val program: Program): IAstProcessor {
|
|
|
|
private val prog: IntermediateProgram = IntermediateProgram(program.name, program.loadAddress, program.heap, program.modules.first().source)
|
|
private var generatedLabelSequenceNumber = 0
|
|
private val breakStmtLabelStack : Stack<String> = Stack()
|
|
private val continueStmtLabelStack : Stack<String> = Stack()
|
|
|
|
fun compile(options: CompilationOptions) : IntermediateProgram {
|
|
println("Creating stackVM code...")
|
|
program.modules.forEach {
|
|
process(it)
|
|
}
|
|
return prog
|
|
}
|
|
|
|
override fun process(block: Block): IStatement {
|
|
prog.newBlock(block.name, block.address, block.options())
|
|
processVariables(block)
|
|
prog.line(block.position)
|
|
translate(block.statements)
|
|
return super.process(block)
|
|
}
|
|
|
|
private fun processVariables(scope: INameScope) {
|
|
for(variable in scope.statements.filterIsInstance<VarDecl>())
|
|
prog.variable(variable.scopedname, variable)
|
|
for(subscope in scope.subScopes())
|
|
processVariables(subscope.value)
|
|
}
|
|
|
|
override fun process(subroutine: Subroutine): IStatement {
|
|
if(subroutine.asmAddress==null) {
|
|
prog.label(subroutine.scopedname, true)
|
|
prog.instr(Opcode.START_PROCDEF)
|
|
prog.line(subroutine.position)
|
|
// note: the caller has already written the arguments into the subroutine's parameter variables.
|
|
// note2: don't separate normal and VariableInitializationAssignment here, because the order strictly matters
|
|
translate(subroutine.statements)
|
|
val r= super.process(subroutine)
|
|
prog.instr(Opcode.END_PROCDEF)
|
|
return r
|
|
} else {
|
|
// asmsub
|
|
if(subroutine.containsCodeOrVars())
|
|
throw CompilerException("kernel subroutines (with memory address) can't have a body: $subroutine")
|
|
|
|
prog.memoryPointer(subroutine.scopedname, subroutine.asmAddress, DataType.UBYTE) // the datatype is a bit of a dummy in this case
|
|
return super.process(subroutine)
|
|
}
|
|
}
|
|
|
|
private fun translate(statements: List<IStatement>) {
|
|
for (stmt: IStatement in statements) {
|
|
generatedLabelSequenceNumber++
|
|
when (stmt) {
|
|
is Label -> translate(stmt)
|
|
is Assignment -> translate(stmt) // normal and augmented assignments
|
|
is PostIncrDecr -> translate(stmt)
|
|
is Jump -> translate(stmt, null)
|
|
is FunctionCallStatement -> translate(stmt)
|
|
is IfStatement -> translate(stmt)
|
|
is BranchStatement -> translate(stmt)
|
|
is Break -> translate(stmt)
|
|
is Continue -> translate(stmt)
|
|
is ForLoop -> translate(stmt)
|
|
is WhileLoop -> translate(stmt)
|
|
is RepeatLoop -> translate(stmt)
|
|
is AnonymousScope -> translate(stmt)
|
|
is ReturnFromIrq -> translate(stmt)
|
|
is Return -> translate(stmt)
|
|
is Directive -> {
|
|
when(stmt.directive) {
|
|
"%asminclude" -> translateAsmInclude(stmt.args, prog.source)
|
|
"%asmbinary" -> translateAsmBinary(stmt.args)
|
|
"%breakpoint" -> {
|
|
prog.line(stmt.position)
|
|
prog.instr(Opcode.BREAKPOINT)
|
|
}
|
|
}
|
|
}
|
|
is VarDecl, is Subroutine -> {} // skip this, already processed these.
|
|
is NopStatement -> {}
|
|
is InlineAssembly -> translate(stmt)
|
|
else -> TODO("translate statement $stmt to stackvm")
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun opcodePush(dt: DataType): Opcode {
|
|
return when (dt) {
|
|
in ByteDatatypes -> Opcode.PUSH_BYTE
|
|
in WordDatatypes -> Opcode.PUSH_WORD
|
|
in IterableDatatypes -> Opcode.PUSH_WORD
|
|
DataType.FLOAT -> Opcode.PUSH_FLOAT
|
|
else -> throw CompilerException("invalid dt $dt")
|
|
}
|
|
}
|
|
|
|
private fun opcodeAdd(dt: DataType): Opcode {
|
|
return when (dt) {
|
|
DataType.UBYTE -> Opcode.ADD_UB
|
|
DataType.BYTE -> Opcode.ADD_B
|
|
DataType.UWORD -> Opcode.ADD_UW
|
|
DataType.WORD -> Opcode.ADD_W
|
|
DataType.FLOAT -> Opcode.ADD_F
|
|
else -> throw CompilerException("invalid dt $dt")
|
|
}
|
|
}
|
|
|
|
private fun opcodeSub(dt: DataType): Opcode {
|
|
return when (dt) {
|
|
DataType.UBYTE -> Opcode.SUB_UB
|
|
DataType.BYTE -> Opcode.SUB_B
|
|
DataType.UWORD -> Opcode.SUB_UW
|
|
DataType.WORD -> Opcode.SUB_W
|
|
DataType.FLOAT -> Opcode.SUB_F
|
|
else -> throw CompilerException("invalid dt $dt")
|
|
}
|
|
}
|
|
|
|
private fun opcodeCompare(dt: DataType): Opcode {
|
|
return when (dt) {
|
|
DataType.UBYTE -> Opcode.CMP_UB
|
|
DataType.BYTE -> Opcode.CMP_B
|
|
DataType.UWORD -> Opcode.CMP_UW
|
|
DataType.WORD -> Opcode.CMP_W
|
|
else -> throw CompilerException("invalid dt $dt")
|
|
}
|
|
}
|
|
|
|
private fun opcodePushvar(dt: DataType): Opcode {
|
|
return when (dt) {
|
|
in ByteDatatypes -> Opcode.PUSH_VAR_BYTE
|
|
in WordDatatypes -> Opcode.PUSH_VAR_WORD
|
|
in IterableDatatypes -> Opcode.PUSH_ADDR_HEAPVAR
|
|
DataType.FLOAT -> Opcode.PUSH_VAR_FLOAT
|
|
else -> throw CompilerException("invalid dt $dt")
|
|
}
|
|
}
|
|
|
|
private fun opcodeReadindexedvar(dt: DataType): Opcode {
|
|
return when (dt) {
|
|
DataType.ARRAY_UB, DataType.ARRAY_B -> Opcode.READ_INDEXED_VAR_BYTE
|
|
DataType.ARRAY_UW, DataType.ARRAY_W -> Opcode.READ_INDEXED_VAR_WORD
|
|
DataType.ARRAY_F -> Opcode.READ_INDEXED_VAR_FLOAT
|
|
DataType.STR, DataType.STR_S -> Opcode.READ_INDEXED_VAR_BYTE
|
|
else -> throw CompilerException("invalid dt for indexed access $dt")
|
|
}
|
|
}
|
|
|
|
private fun opcodeWriteindexedvar(dt: DataType): Opcode {
|
|
return when (dt) {
|
|
DataType.ARRAY_UB, DataType.ARRAY_B -> Opcode.WRITE_INDEXED_VAR_BYTE
|
|
DataType.ARRAY_UW, DataType.ARRAY_W -> Opcode.WRITE_INDEXED_VAR_WORD
|
|
DataType.ARRAY_F -> Opcode.WRITE_INDEXED_VAR_FLOAT
|
|
DataType.STR, DataType.STR_S -> Opcode.WRITE_INDEXED_VAR_BYTE
|
|
else -> throw CompilerException("invalid dt for indexed access $dt")
|
|
}
|
|
}
|
|
|
|
private fun opcodeDiscard(dt: DataType): Opcode {
|
|
return when(dt) {
|
|
in ByteDatatypes -> Opcode.DISCARD_BYTE
|
|
in WordDatatypes -> Opcode.DISCARD_WORD
|
|
in IterableDatatypes -> Opcode.DISCARD_WORD
|
|
DataType.FLOAT -> Opcode.DISCARD_FLOAT
|
|
else -> throw CompilerException("invalid dt $dt")
|
|
}
|
|
}
|
|
|
|
private fun opcodePopvar(dt: DataType): Opcode {
|
|
return when (dt) {
|
|
in ByteDatatypes -> Opcode.POP_VAR_BYTE
|
|
in WordDatatypes -> Opcode.POP_VAR_WORD
|
|
in IterableDatatypes -> Opcode.POP_VAR_WORD
|
|
DataType.FLOAT -> Opcode.POP_VAR_FLOAT
|
|
else -> throw CompilerException("invalid dt $dt")
|
|
}
|
|
}
|
|
|
|
private fun opcodePopmem(dt: DataType): Opcode {
|
|
return when (dt) {
|
|
in ByteDatatypes -> Opcode.POP_MEM_BYTE
|
|
in WordDatatypes -> Opcode.POP_MEM_WORD
|
|
in IterableDatatypes -> Opcode.POP_MEM_WORD
|
|
DataType.FLOAT -> Opcode.POP_MEM_FLOAT
|
|
else -> throw CompilerException("invalid dt $dt")
|
|
}
|
|
}
|
|
|
|
private fun opcodeDecvar(dt: DataType): Opcode {
|
|
return when(dt) {
|
|
DataType.UBYTE -> Opcode.DEC_VAR_UB
|
|
DataType.BYTE -> Opcode.DEC_VAR_B
|
|
DataType.UWORD -> Opcode.DEC_VAR_UW
|
|
DataType.WORD -> Opcode.DEC_VAR_W
|
|
DataType.FLOAT -> Opcode.DEC_VAR_F
|
|
else -> throw CompilerException("can't dec type $dt")
|
|
}
|
|
}
|
|
|
|
private fun opcodeIncvar(dt: DataType): Opcode {
|
|
return when(dt) {
|
|
DataType.UBYTE -> Opcode.INC_VAR_UB
|
|
DataType.BYTE -> Opcode.INC_VAR_B
|
|
DataType.UWORD -> Opcode.INC_VAR_UW
|
|
DataType.WORD -> Opcode.INC_VAR_W
|
|
DataType.FLOAT -> Opcode.INC_VAR_F
|
|
else -> throw CompilerException("can't inc type $dt")
|
|
}
|
|
}
|
|
|
|
private fun opcodeIncArrayindexedVar(dt: DataType): Opcode {
|
|
return when(dt) {
|
|
DataType.ARRAY_UB -> Opcode.INC_INDEXED_VAR_UB
|
|
DataType.ARRAY_B -> Opcode.INC_INDEXED_VAR_B
|
|
DataType.ARRAY_UW -> Opcode.INC_INDEXED_VAR_UW
|
|
DataType.ARRAY_W -> Opcode.INC_INDEXED_VAR_W
|
|
DataType.ARRAY_F -> Opcode.INC_INDEXED_VAR_FLOAT
|
|
else -> throw CompilerException("can't inc type $dt")
|
|
}
|
|
}
|
|
|
|
private fun opcodeDecArrayindexedVar(dt: DataType): Opcode {
|
|
return when(dt) {
|
|
DataType.ARRAY_UB -> Opcode.DEC_INDEXED_VAR_UB
|
|
DataType.ARRAY_B -> Opcode.DEC_INDEXED_VAR_B
|
|
DataType.ARRAY_UW -> Opcode.DEC_INDEXED_VAR_UW
|
|
DataType.ARRAY_W -> Opcode.DEC_INDEXED_VAR_W
|
|
DataType.ARRAY_F -> Opcode.DEC_INDEXED_VAR_FLOAT
|
|
else -> throw CompilerException("can't dec type $dt")
|
|
}
|
|
}
|
|
|
|
private fun translate(stmt: InlineAssembly) {
|
|
// If the inline assembly is the only statement inside a subroutine (except vardecls),
|
|
// we can use the name of that subroutine to identify it.
|
|
// The compiler could then convert it to a special system call
|
|
val sub = stmt.parent as? Subroutine
|
|
val scopename =
|
|
if(sub!=null && sub.statements.filter{it !is VarDecl }.size==1)
|
|
sub.scopedname
|
|
else
|
|
null
|
|
prog.instr(Opcode.INLINE_ASSEMBLY, callLabel=scopename, callLabel2 = stmt.assembly)
|
|
}
|
|
|
|
private fun translate(stmt: Continue) {
|
|
prog.line(stmt.position)
|
|
if(continueStmtLabelStack.empty())
|
|
throw CompilerException("continue outside of loop statement block")
|
|
val label = continueStmtLabelStack.peek()
|
|
prog.instr(Opcode.JUMP, callLabel = label)
|
|
}
|
|
|
|
private fun translate(stmt: Break) {
|
|
prog.line(stmt.position)
|
|
if(breakStmtLabelStack.empty())
|
|
throw CompilerException("break outside of loop statement block")
|
|
val label = breakStmtLabelStack.peek()
|
|
prog.instr(Opcode.JUMP, callLabel = label)
|
|
}
|
|
|
|
private fun translate(branch: BranchStatement) {
|
|
/*
|
|
* A branch: IF_CC { stuff } else { other_stuff }
|
|
* Which is translated into:
|
|
* BCS _stmt_999_else
|
|
* stuff
|
|
* JUMP _stmt_999_end
|
|
* _stmt_999_else:
|
|
* other_stuff ;; optional
|
|
* _stmt_999_end:
|
|
* nop
|
|
*
|
|
* if the branch statement just contains jumps, more efficient code is generated.
|
|
* (just the appropriate branching instruction is outputted!)
|
|
*/
|
|
if(branch.elsepart.containsNoCodeNorVars() && branch.truepart.containsNoCodeNorVars())
|
|
return
|
|
|
|
fun branchOpcode(branch: BranchStatement, complement: Boolean) =
|
|
if(complement) {
|
|
when (branch.condition) {
|
|
BranchCondition.CS -> Opcode.BCC
|
|
BranchCondition.CC -> Opcode.BCS
|
|
BranchCondition.EQ, BranchCondition.Z -> Opcode.BNZ
|
|
BranchCondition.NE, BranchCondition.NZ -> Opcode.BZ
|
|
BranchCondition.VS -> Opcode.BVC
|
|
BranchCondition.VC -> Opcode.BVS
|
|
BranchCondition.MI, BranchCondition.NEG -> Opcode.BPOS
|
|
BranchCondition.PL, BranchCondition.POS -> Opcode.BNEG
|
|
}
|
|
} else {
|
|
when (branch.condition) {
|
|
BranchCondition.CS -> Opcode.BCS
|
|
BranchCondition.CC -> Opcode.BCC
|
|
BranchCondition.EQ, BranchCondition.Z -> Opcode.BZ
|
|
BranchCondition.NE, BranchCondition.NZ -> Opcode.BNZ
|
|
BranchCondition.VS -> Opcode.BVS
|
|
BranchCondition.VC -> Opcode.BVC
|
|
BranchCondition.MI, BranchCondition.NEG -> Opcode.BNEG
|
|
BranchCondition.PL, BranchCondition.POS -> Opcode.BPOS
|
|
}
|
|
}
|
|
|
|
prog.line(branch.position)
|
|
val truejump = branch.truepart.statements.first()
|
|
val elsejump = branch.elsepart.statements.firstOrNull()
|
|
if(truejump is Jump && truejump.address==null && (elsejump ==null || (elsejump is Jump && elsejump.address==null))) {
|
|
// optimized code for just conditional jumping
|
|
val opcodeTrue = branchOpcode(branch, false)
|
|
translate(truejump, opcodeTrue)
|
|
if(elsejump is Jump) {
|
|
val opcodeFalse = branchOpcode(branch, true)
|
|
translate(elsejump, opcodeFalse)
|
|
}
|
|
} else {
|
|
// regular if..else branching
|
|
val labelElse = makeLabel(branch, "else")
|
|
val labelEnd = makeLabel(branch, "end")
|
|
val opcode = branchOpcode(branch, true)
|
|
if (branch.elsepart.containsNoCodeNorVars()) {
|
|
prog.instr(opcode, callLabel = labelEnd)
|
|
translate(branch.truepart)
|
|
prog.label(labelEnd)
|
|
} else {
|
|
prog.instr(opcode, callLabel = labelElse)
|
|
translate(branch.truepart)
|
|
prog.instr(Opcode.JUMP, callLabel = labelEnd)
|
|
prog.label(labelElse)
|
|
translate(branch.elsepart)
|
|
prog.label(labelEnd)
|
|
}
|
|
prog.instr(Opcode.NOP)
|
|
}
|
|
}
|
|
|
|
private fun makeLabel(scopeStmt: IStatement, postfix: String): String {
|
|
generatedLabelSequenceNumber++
|
|
return "${scopeStmt.makeScopedName("")}.<s-$generatedLabelSequenceNumber-$postfix>"
|
|
}
|
|
|
|
private fun translate(stmt: IfStatement) {
|
|
/*
|
|
* An IF statement: IF (condition-expression) { stuff } else { other_stuff }
|
|
* Which is translated into:
|
|
* <condition-expression evaluation>
|
|
* JZ/JZW _stmt_999_else
|
|
* stuff
|
|
* JUMP _stmt_999_end
|
|
* _stmt_999_else:
|
|
* other_stuff ;; optional
|
|
* _stmt_999_end:
|
|
* nop
|
|
*
|
|
* or when there is no else block:
|
|
* <condition-expression evaluation>
|
|
* JZ/JZW _stmt_999_end
|
|
* stuff
|
|
* _stmt_999_end:
|
|
* nop
|
|
*
|
|
* For if statements with goto's, more efficient code is generated.
|
|
*/
|
|
prog.line(stmt.position)
|
|
translate(stmt.condition)
|
|
|
|
val trueGoto = stmt.truepart.statements.singleOrNull() as? Jump
|
|
if(trueGoto!=null) {
|
|
// optimization for if (condition) goto ....
|
|
val conditionJumpOpcode = when(stmt.condition.inferType(program)) {
|
|
in ByteDatatypes -> Opcode.JNZ
|
|
in WordDatatypes -> Opcode.JNZW
|
|
else -> throw CompilerException("invalid condition datatype (expected byte or word) $stmt")
|
|
}
|
|
translate(trueGoto, conditionJumpOpcode)
|
|
translate(stmt.elsepart)
|
|
return
|
|
}
|
|
|
|
val conditionJumpOpcode = when(stmt.condition.inferType(program)) {
|
|
in ByteDatatypes -> Opcode.JZ
|
|
in WordDatatypes -> Opcode.JZW
|
|
else -> throw CompilerException("invalid condition datatype (expected byte or word) $stmt")
|
|
}
|
|
val labelEnd = makeLabel(stmt, "end")
|
|
if(stmt.elsepart.containsNoCodeNorVars()) {
|
|
prog.instr(conditionJumpOpcode, callLabel = labelEnd)
|
|
translate(stmt.truepart)
|
|
prog.label(labelEnd)
|
|
} else {
|
|
val labelElse = makeLabel(stmt, "else")
|
|
prog.instr(conditionJumpOpcode, callLabel = labelElse)
|
|
translate(stmt.truepart)
|
|
prog.instr(Opcode.JUMP, callLabel = labelEnd)
|
|
prog.label(labelElse)
|
|
translate(stmt.elsepart)
|
|
prog.label(labelEnd)
|
|
}
|
|
prog.instr(Opcode.NOP)
|
|
}
|
|
|
|
private fun translate(expr: IExpression) {
|
|
when(expr) {
|
|
is RegisterExpr -> {
|
|
prog.instr(Opcode.PUSH_VAR_BYTE, callLabel = expr.register.name)
|
|
}
|
|
is PrefixExpression -> {
|
|
translate(expr.expression)
|
|
translatePrefixOperator(expr.operator, expr.expression.inferType(program))
|
|
}
|
|
is BinaryExpression -> {
|
|
val leftDt = expr.left.inferType(program)!!
|
|
val rightDt = expr.right.inferType(program)!!
|
|
val (commonDt, _) = expr.commonDatatype(leftDt, rightDt, expr.left, expr.right)
|
|
translate(expr.left)
|
|
if(leftDt!=commonDt)
|
|
convertType(leftDt, commonDt)
|
|
translate(expr.right)
|
|
if(rightDt!=commonDt)
|
|
convertType(rightDt, commonDt)
|
|
if(expr.operator=="<<" || expr.operator==">>")
|
|
translateBitshiftedOperator(expr.operator, leftDt, expr.right.constValue(program))
|
|
else
|
|
translateBinaryOperator(expr.operator, commonDt)
|
|
}
|
|
is FunctionCall -> {
|
|
val target = expr.target.targetStatement(program.namespace)
|
|
if(target is BuiltinFunctionStatementPlaceholder) {
|
|
// call to a builtin function (some will just be an opcode!)
|
|
val funcname = expr.target.nameInSource[0]
|
|
translateBuiltinFunctionCall(funcname, expr.arglist)
|
|
} else {
|
|
if (target is Subroutine) translateSubroutineCall(target, expr.arglist, expr.position)
|
|
else TODO("non-builtin-function call to $target")
|
|
}
|
|
}
|
|
is IdentifierReference -> translate(expr)
|
|
is ArrayIndexedExpression -> translate(expr, false)
|
|
is RangeExpr -> throw CompilerException("it's not possible to just have a range expression that has to be translated")
|
|
is TypecastExpression -> translate(expr)
|
|
is DirectMemoryRead -> translate(expr)
|
|
is DirectMemoryWrite -> translate(expr)
|
|
is AddressOf -> translate(expr)
|
|
else -> {
|
|
val lv = expr.constValue(program) ?: throw CompilerException("constant expression required, not $expr")
|
|
when(lv.type) {
|
|
in ByteDatatypes -> prog.instr(Opcode.PUSH_BYTE, RuntimeValue(lv.type, lv.bytevalue!!))
|
|
in WordDatatypes -> prog.instr(Opcode.PUSH_WORD, RuntimeValue(lv.type, lv.wordvalue!!))
|
|
DataType.FLOAT -> prog.instr(Opcode.PUSH_FLOAT, RuntimeValue(lv.type, lv.floatvalue!!))
|
|
in StringDatatypes -> {
|
|
if(lv.heapId==null)
|
|
throw CompilerException("string should have been moved into heap ${lv.position}")
|
|
TODO("push address of string with PUSH_ADDR_HEAPVAR")
|
|
}
|
|
in ArrayDatatypes -> {
|
|
if(lv.heapId==null)
|
|
throw CompilerException("array should have been moved into heap ${lv.position}")
|
|
TODO("push address of array with PUSH_ADDR_HEAPVAR")
|
|
}
|
|
else -> throw CompilerException("weird datatype")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private fun tryConvertType(givenDt: DataType, targetDt: DataType): Boolean {
|
|
return try {
|
|
convertType(givenDt, targetDt)
|
|
true
|
|
} catch (x: CompilerException) {
|
|
false
|
|
}
|
|
}
|
|
|
|
|
|
private fun convertType(givenDt: DataType, targetDt: DataType) {
|
|
// only WIDENS a type, never NARROWS. To avoid loss of precision.
|
|
if(givenDt==targetDt)
|
|
return
|
|
if(givenDt !in NumericDatatypes)
|
|
throw CompilerException("converting non-numeric $givenDt")
|
|
if(targetDt !in NumericDatatypes)
|
|
throw CompilerException("converting $givenDt to non-numeric $targetDt")
|
|
when(givenDt) {
|
|
DataType.UBYTE -> when(targetDt) {
|
|
DataType.UWORD -> prog.instr(Opcode.CAST_UB_TO_UW)
|
|
DataType.WORD -> prog.instr(Opcode.CAST_UB_TO_W)
|
|
DataType.FLOAT -> prog.instr(Opcode.CAST_UB_TO_F)
|
|
else -> {}
|
|
}
|
|
DataType.BYTE -> when(targetDt) {
|
|
DataType.UWORD -> prog.instr(Opcode.CAST_B_TO_UW)
|
|
DataType.WORD -> prog.instr(Opcode.CAST_B_TO_W)
|
|
DataType.FLOAT -> prog.instr(Opcode.CAST_B_TO_F)
|
|
else -> {}
|
|
}
|
|
DataType.UWORD -> when(targetDt) {
|
|
in ByteDatatypes -> throw CompilerException("narrowing type")
|
|
DataType.FLOAT -> prog.instr(Opcode.CAST_UW_TO_F)
|
|
else -> {}
|
|
}
|
|
DataType.WORD -> when(targetDt) {
|
|
in ByteDatatypes -> throw CompilerException("narrowing type")
|
|
DataType.FLOAT -> prog.instr(Opcode.CAST_W_TO_F)
|
|
else -> {}
|
|
}
|
|
DataType.FLOAT -> if(targetDt in IntegerDatatypes) throw CompilerException("narrowing type")
|
|
else -> {}
|
|
}
|
|
}
|
|
|
|
private fun translate(identifierRef: IdentifierReference) {
|
|
val target = identifierRef.targetStatement(program.namespace)
|
|
when (target) {
|
|
is VarDecl -> {
|
|
when (target.type) {
|
|
VarDeclType.VAR -> {
|
|
val opcode = opcodePushvar(target.datatype)
|
|
prog.instr(opcode, callLabel = target.scopedname)
|
|
}
|
|
VarDeclType.CONST ->
|
|
throw CompilerException("const ref should have been const-folded away")
|
|
VarDeclType.MEMORY -> {
|
|
when (target.datatype) {
|
|
DataType.UBYTE -> prog.instr(Opcode.PUSH_MEM_UB, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
|
|
DataType.BYTE-> prog.instr(Opcode.PUSH_MEM_B, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
|
|
DataType.UWORD -> prog.instr(Opcode.PUSH_MEM_UW, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
|
|
DataType.WORD -> prog.instr(Opcode.PUSH_MEM_W, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
|
|
DataType.FLOAT -> prog.instr(Opcode.PUSH_MEM_FLOAT, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
|
|
else -> throw CompilerException("invalid datatype for memory variable expression: $target")
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
else -> throw CompilerException("expression identifierref should be a vardef, not $target")
|
|
}
|
|
}
|
|
|
|
private fun translate(stmt: FunctionCallStatement) {
|
|
prog.line(stmt.position)
|
|
val targetStmt = stmt.target.targetStatement(program.namespace)!!
|
|
if(targetStmt is BuiltinFunctionStatementPlaceholder) {
|
|
val funcname = stmt.target.nameInSource[0]
|
|
translateBuiltinFunctionCall(funcname, stmt.arglist)
|
|
return
|
|
}
|
|
|
|
when(targetStmt) {
|
|
is Label ->
|
|
prog.instr(Opcode.CALL, callLabel = targetStmt.scopedname)
|
|
is Subroutine -> {
|
|
translateSubroutineCall(targetStmt, stmt.arglist, stmt.position)
|
|
// make sure we clean up the unused result values from the stack
|
|
for(rv in targetStmt.returntypes) {
|
|
val opcode=opcodeDiscard(rv)
|
|
prog.instr(opcode)
|
|
}
|
|
}
|
|
else ->
|
|
throw AstException("invalid call target node type: ${targetStmt::class}")
|
|
}
|
|
}
|
|
|
|
private fun translateBuiltinFunctionCall(funcname: String, args: List<IExpression>) {
|
|
// some builtin functions are implemented directly as vm opcodes
|
|
|
|
if(funcname == "swap") {
|
|
translateSwap(args)
|
|
return
|
|
}
|
|
|
|
val builtinFuncParams = BuiltinFunctions[funcname]?.parameters
|
|
args.forEachIndexed { index, arg ->
|
|
// place function argument(s) on the stack
|
|
translate(arg)
|
|
// cast type if needed
|
|
if(builtinFuncParams!=null) {
|
|
val paramDts = builtinFuncParams[index].possibleDatatypes
|
|
val argDt = arg.inferType(program)!!
|
|
if(argDt !in paramDts) {
|
|
for(paramDt in paramDts.sorted())
|
|
if(tryConvertType(argDt, paramDt))
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
when (funcname) {
|
|
"len" -> {
|
|
// 1 argument, type determines the exact syscall to use
|
|
val arg=args.single()
|
|
when (arg.inferType(program)) {
|
|
DataType.STR, DataType.STR_S -> createSyscall("${funcname}_str")
|
|
else -> throw CompilerException("wrong datatype for len()")
|
|
}
|
|
}
|
|
"any", "all" -> {
|
|
// 1 array argument, type determines the exact syscall to use
|
|
val arg=args.single() as IdentifierReference
|
|
val target=arg.targetVarDecl(program.namespace)!!
|
|
val length= RuntimeValue(DataType.UBYTE, target.arraysize!!.size()!!)
|
|
prog.instr(Opcode.PUSH_BYTE, length)
|
|
when (arg.inferType(program)) {
|
|
DataType.ARRAY_B, DataType.ARRAY_UB -> createSyscall("${funcname}_b")
|
|
DataType.ARRAY_W, DataType.ARRAY_UW -> createSyscall("${funcname}_w")
|
|
DataType.ARRAY_F -> createSyscall("${funcname}_f")
|
|
else -> throw CompilerException("wrong datatype for $funcname()")
|
|
}
|
|
}
|
|
"avg" -> {
|
|
// 1 array argument, type determines the exact syscall to use
|
|
val arg=args.single() as IdentifierReference
|
|
val target=arg.targetVarDecl(program.namespace)!!
|
|
val length= RuntimeValue(DataType.UBYTE, target.arraysize!!.size()!!)
|
|
val arrayDt=arg.inferType(program)
|
|
prog.instr(Opcode.PUSH_BYTE, length)
|
|
when (arrayDt) {
|
|
DataType.ARRAY_UB -> {
|
|
createSyscall("sum_ub")
|
|
prog.instr(Opcode.CAST_UW_TO_F) // result of sum(ubyte) is uword, so cast
|
|
}
|
|
DataType.ARRAY_B -> {
|
|
createSyscall("sum_b")
|
|
prog.instr(Opcode.CAST_W_TO_F) // result of sum(byte) is word, so cast
|
|
}
|
|
DataType.ARRAY_UW -> {
|
|
createSyscall("sum_uw")
|
|
prog.instr(Opcode.CAST_UW_TO_F) // result of sum(uword) is uword, so cast
|
|
}
|
|
DataType.ARRAY_W -> {
|
|
createSyscall("sum_w")
|
|
prog.instr(Opcode.CAST_W_TO_F) // result of sum(word) is word, so cast
|
|
}
|
|
DataType.ARRAY_F -> createSyscall("sum_f")
|
|
else -> throw CompilerException("wrong datatype for avg")
|
|
}
|
|
// divide by the number of elements
|
|
prog.instr(opcodePush(DataType.FLOAT), RuntimeValue(DataType.FLOAT, length.numericValue()))
|
|
prog.instr(Opcode.DIV_F)
|
|
}
|
|
"min", "max", "sum" -> {
|
|
// 1 array argument, type determines the exact syscall to use
|
|
val arg=args.single() as IdentifierReference
|
|
val target=arg.targetVarDecl(program.namespace)!!
|
|
val length= RuntimeValue(DataType.UBYTE, target.arraysize!!.size()!!)
|
|
prog.instr(Opcode.PUSH_BYTE, length)
|
|
when (arg.inferType(program)) {
|
|
DataType.ARRAY_UB -> createSyscall("${funcname}_ub")
|
|
DataType.ARRAY_B -> createSyscall("${funcname}_b")
|
|
DataType.ARRAY_UW -> createSyscall("${funcname}_uw")
|
|
DataType.ARRAY_W -> createSyscall("${funcname}_w")
|
|
DataType.ARRAY_F -> createSyscall("${funcname}_f")
|
|
else -> throw CompilerException("wrong datatype for $funcname()")
|
|
}
|
|
}
|
|
"abs" -> {
|
|
// 1 argument, type determines the exact opcode to use
|
|
val arg = args.single()
|
|
when (arg.inferType(program)) {
|
|
DataType.UBYTE, DataType.UWORD -> {}
|
|
DataType.BYTE -> prog.instr(Opcode.ABS_B)
|
|
DataType.WORD -> prog.instr(Opcode.ABS_W)
|
|
DataType.FLOAT -> prog.instr(Opcode.ABS_F)
|
|
else -> throw CompilerException("wrong datatype for $funcname()")
|
|
}
|
|
}
|
|
"msb" -> prog.instr(Opcode.MSB) // note: "lsb" is not a function at all, it's just an alias for the cast "... as ubyte"
|
|
"mkword" -> prog.instr(Opcode.MKWORD)
|
|
"lsl" -> {
|
|
val arg = args.single()
|
|
val dt = arg.inferType(program)
|
|
when (dt) {
|
|
in ByteDatatypes -> prog.instr(Opcode.SHL_BYTE)
|
|
in WordDatatypes -> prog.instr(Opcode.SHL_WORD)
|
|
else -> throw CompilerException("wrong datatype")
|
|
}
|
|
// this function doesn't return a value on the stack so we pop it directly into the argument register/variable again
|
|
popValueIntoTarget(AssignTarget.fromExpr(arg), dt!!)
|
|
}
|
|
"lsr" -> {
|
|
val arg = args.single()
|
|
val dt = arg.inferType(program)
|
|
when (dt) {
|
|
DataType.UBYTE -> prog.instr(Opcode.SHR_UBYTE)
|
|
DataType.BYTE -> prog.instr(Opcode.SHR_SBYTE)
|
|
DataType.UWORD -> prog.instr(Opcode.SHR_UWORD)
|
|
DataType.WORD -> prog.instr(Opcode.SHR_SWORD)
|
|
else -> throw CompilerException("wrong datatype")
|
|
}
|
|
// this function doesn't return a value on the stack so we pop it directly into the argument register/variable again
|
|
popValueIntoTarget(AssignTarget.fromExpr(arg), dt)
|
|
}
|
|
"rol" -> {
|
|
val arg = args.single()
|
|
val dt = arg.inferType(program)
|
|
when (dt) {
|
|
in ByteDatatypes -> prog.instr(Opcode.ROL_BYTE)
|
|
in WordDatatypes -> prog.instr(Opcode.ROL_WORD)
|
|
else -> throw CompilerException("wrong datatype")
|
|
}
|
|
// this function doesn't return a value on the stack so we pop it directly into the argument register/variable again
|
|
popValueIntoTarget(AssignTarget.fromExpr(arg), dt!!)
|
|
}
|
|
"ror" -> {
|
|
val arg = args.single()
|
|
val dt = arg.inferType(program)
|
|
when (dt) {
|
|
in ByteDatatypes -> prog.instr(Opcode.ROR_BYTE)
|
|
in WordDatatypes -> prog.instr(Opcode.ROR_WORD)
|
|
else -> throw CompilerException("wrong datatype")
|
|
}
|
|
// this function doesn't return a value on the stack so we pop it directly into the argument register/variable again
|
|
popValueIntoTarget(AssignTarget.fromExpr(arg), dt!!)
|
|
}
|
|
"rol2" -> {
|
|
val arg = args.single()
|
|
val dt = arg.inferType(program)
|
|
when (dt) {
|
|
in ByteDatatypes -> prog.instr(Opcode.ROL2_BYTE)
|
|
in WordDatatypes -> prog.instr(Opcode.ROL2_WORD)
|
|
else -> throw CompilerException("wrong datatype")
|
|
}
|
|
// this function doesn't return a value on the stack so we pop it directly into the argument register/variable again
|
|
popValueIntoTarget(AssignTarget.fromExpr(arg), dt!!)
|
|
}
|
|
"ror2" -> {
|
|
val arg = args.single()
|
|
val dt = arg.inferType(program)
|
|
when (dt) {
|
|
in ByteDatatypes -> prog.instr(Opcode.ROR2_BYTE)
|
|
in WordDatatypes -> prog.instr(Opcode.ROR2_WORD)
|
|
else -> throw CompilerException("wrong datatype")
|
|
}
|
|
// this function doesn't return a value on the stack so we pop it directly into the argument register/variable again
|
|
popValueIntoTarget(AssignTarget.fromExpr(arg), dt!!)
|
|
}
|
|
"set_carry" -> prog.instr(Opcode.SEC)
|
|
"clear_carry" -> prog.instr(Opcode.CLC)
|
|
"set_irqd" -> prog.instr(Opcode.SEI)
|
|
"clear_irqd" -> prog.instr(Opcode.CLI)
|
|
"rsave" -> prog.instr(Opcode.RSAVE)
|
|
"rrestore" -> prog.instr(Opcode.RRESTORE)
|
|
else -> createSyscall(funcname) // call builtin function
|
|
}
|
|
}
|
|
|
|
private fun translateSwap(args: List<IExpression>) {
|
|
// swap(x,y) is treated differently, it's not a normal function call
|
|
if (args.size != 2)
|
|
throw AstException("swap requires 2 arguments")
|
|
val dt1 = args[0].inferType(program)!!
|
|
val dt2 = args[1].inferType(program)!!
|
|
if (dt1 != dt2)
|
|
throw AstException("swap requires 2 args of identical type")
|
|
if (args[0].constValue(program) != null || args[1].constValue(program) != null)
|
|
throw AstException("swap requires 2 variables, not constant value(s)")
|
|
if(args[0] isSameAs args[1])
|
|
throw AstException("swap should have 2 different args")
|
|
if(dt1 !in NumericDatatypes)
|
|
throw AstException("swap requires args of numerical type")
|
|
|
|
translate(args[0])
|
|
translate(args[1])
|
|
// pop in reverse order
|
|
popValueIntoTarget(AssignTarget.fromExpr(args[0]), dt1)
|
|
popValueIntoTarget(AssignTarget.fromExpr(args[1]), dt2)
|
|
return
|
|
}
|
|
|
|
private fun translateSubroutineCall(subroutine: Subroutine, arguments: List<IExpression>, callPosition: Position) {
|
|
// evaluate the arguments and assign them into the subroutine's argument variables.
|
|
var restoreX = Register.X in subroutine.asmClobbers
|
|
if(restoreX)
|
|
prog.instr(Opcode.RSAVEX)
|
|
// We don't bother about saving A and Y. They're considered expendable.
|
|
|
|
if(subroutine.isAsmSubroutine) {
|
|
restoreX = translateAsmSubCallArguments(subroutine, arguments, callPosition, restoreX)
|
|
} else {
|
|
// only regular (non-register) arguments
|
|
// "assign" the arguments to the locally scoped parameter variables for this subroutine
|
|
// (subroutine arguments are not passed via the stack!)
|
|
for (arg in arguments.zip(subroutine.parameters)) {
|
|
translate(arg.first)
|
|
convertType(arg.first.inferType(program)!!, arg.second.type) // convert types of arguments to required parameter type
|
|
val opcode = opcodePopvar(arg.second.type)
|
|
prog.instr(opcode, callLabel = subroutine.scopedname + "." + arg.second.name)
|
|
}
|
|
}
|
|
prog.instr(Opcode.CALL, callLabel = subroutine.scopedname)
|
|
if(restoreX)
|
|
prog.instr(Opcode.RRESTOREX)
|
|
|
|
if(subroutine.isAsmSubroutine && subroutine.asmReturnvaluesRegisters.isNotEmpty()) {
|
|
// the result values of the asm-subroutine that are returned in registers, have to be pushed on the stack
|
|
// (in reversed order) otherwise the asm-subroutine can't be used in expressions.
|
|
for(rv in subroutine.asmReturnvaluesRegisters.reversed()) {
|
|
if(rv.statusflag!=null) {
|
|
if (rv.statusflag == Statusflag.Pc) {
|
|
prog.instr(Opcode.CARRY_TO_A)
|
|
prog.instr(Opcode.PUSH_VAR_BYTE, callLabel = Register.A.name)
|
|
}
|
|
else TODO("return value in cpu status flag only supports Carry, not $rv ($subroutine)")
|
|
} else {
|
|
when (rv.registerOrPair) {
|
|
A, X, Y -> prog.instr(Opcode.PUSH_VAR_BYTE, callLabel = rv.registerOrPair.name)
|
|
AX -> prog.instr(Opcode.PUSH_REGAX_WORD)
|
|
AY -> prog.instr(Opcode.PUSH_REGAY_WORD)
|
|
XY -> prog.instr(Opcode.PUSH_REGXY_WORD)
|
|
null -> {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun translateAsmSubCallArguments(subroutine: Subroutine, arguments: List<IExpression>, callPosition: Position, restoreXinitial: Boolean): Boolean {
|
|
var restoreX = restoreXinitial
|
|
if (subroutine.parameters.size != subroutine.asmParameterRegisters.size)
|
|
TODO("no support yet for mix of register and non-register subroutine arguments")
|
|
|
|
// only register arguments (or status-flag bits)
|
|
var carryParam: Boolean? = null
|
|
for (arg in arguments.zip(subroutine.asmParameterRegisters)) {
|
|
if (arg.second.statusflag != null) {
|
|
if (arg.second.statusflag == Statusflag.Pc)
|
|
carryParam = arg.first.constValue(program)!!.asBooleanValue
|
|
else
|
|
throw CompilerException("no support for status flag parameter: ${arg.second.statusflag}")
|
|
} else {
|
|
when (arg.second.registerOrPair!!) {
|
|
A -> {
|
|
val assign = Assignment(listOf(AssignTarget(Register.A, null, null, null, callPosition)), null, arg.first, callPosition)
|
|
assign.linkParents(arguments[0].parent)
|
|
translate(assign)
|
|
}
|
|
X -> {
|
|
if (!restoreX) {
|
|
prog.instr(Opcode.RSAVEX)
|
|
restoreX = true
|
|
}
|
|
val assign = Assignment(listOf(AssignTarget(Register.X, null, null, null, callPosition)), null, arg.first, callPosition)
|
|
assign.linkParents(arguments[0].parent)
|
|
translate(assign)
|
|
}
|
|
Y -> {
|
|
val assign = Assignment(listOf(AssignTarget(Register.Y, null, null, null, callPosition)), null, arg.first, callPosition)
|
|
assign.linkParents(arguments[0].parent)
|
|
translate(assign)
|
|
}
|
|
AX -> {
|
|
if (!restoreX) {
|
|
prog.instr(Opcode.RSAVEX)
|
|
restoreX = true
|
|
}
|
|
val valueA: IExpression
|
|
val valueX: IExpression
|
|
val paramDt = arg.first.inferType(program)
|
|
when (paramDt) {
|
|
DataType.UBYTE -> {
|
|
valueA = arg.first
|
|
valueX = LiteralValue.optimalInteger(0, callPosition)
|
|
val assignA = Assignment(listOf(AssignTarget(Register.A, null, null, null, callPosition)), null, valueA, callPosition)
|
|
val assignX = Assignment(listOf(AssignTarget(Register.X, null, null, null, callPosition)), null, valueX, callPosition)
|
|
assignA.linkParents(arguments[0].parent)
|
|
assignX.linkParents(arguments[0].parent)
|
|
translate(assignA)
|
|
translate(assignX)
|
|
}
|
|
in WordDatatypes -> {
|
|
translate(arg.first)
|
|
prog.instr(Opcode.POP_REGAX_WORD)
|
|
}
|
|
in StringDatatypes + ArrayDatatypes -> throw CompilerException("string or array arguments should have been converted to their pointer value in the Ast $callPosition")
|
|
else -> TODO("pass parameter of type $paramDt in registers AX at $callPosition")
|
|
}
|
|
}
|
|
AY -> {
|
|
val valueA: IExpression
|
|
val valueY: IExpression
|
|
val paramDt = arg.first.inferType(program)
|
|
when (paramDt) {
|
|
DataType.UBYTE -> {
|
|
valueA = arg.first
|
|
valueY = LiteralValue.optimalInteger(0, callPosition)
|
|
val assignA = Assignment(listOf(AssignTarget(Register.A, null, null, null, callPosition)), null, valueA, callPosition)
|
|
val assignY = Assignment(listOf(AssignTarget(Register.Y, null, null, null, callPosition)), null, valueY, callPosition)
|
|
assignA.linkParents(arguments[0].parent)
|
|
assignY.linkParents(arguments[0].parent)
|
|
translate(assignA)
|
|
translate(assignY)
|
|
}
|
|
in WordDatatypes -> {
|
|
translate(arg.first)
|
|
prog.instr(Opcode.POP_REGAY_WORD)
|
|
}
|
|
in StringDatatypes + ArrayDatatypes -> throw CompilerException("string or array arguments should have been converted to their pointer value in the Ast $callPosition")
|
|
else -> TODO("pass parameter of type $paramDt in registers AY at $callPosition")
|
|
}
|
|
}
|
|
XY -> {
|
|
if (!restoreX) {
|
|
prog.instr(Opcode.RSAVEX)
|
|
restoreX = true
|
|
}
|
|
val valueX: IExpression
|
|
val valueY: IExpression
|
|
val paramDt = arg.first.inferType(program)
|
|
when (paramDt) {
|
|
DataType.UBYTE -> {
|
|
valueX = arg.first
|
|
valueY = LiteralValue.optimalInteger(0, callPosition)
|
|
val assignX = Assignment(listOf(AssignTarget(Register.X, null, null, null, callPosition)), null, valueX, callPosition)
|
|
val assignY = Assignment(listOf(AssignTarget(Register.Y, null, null, null, callPosition)), null, valueY, callPosition)
|
|
assignX.linkParents(arguments[0].parent)
|
|
assignY.linkParents(arguments[0].parent)
|
|
translate(assignX)
|
|
translate(assignY)
|
|
}
|
|
in WordDatatypes -> {
|
|
translate(arg.first)
|
|
prog.instr(Opcode.POP_REGXY_WORD)
|
|
}
|
|
in StringDatatypes + ArrayDatatypes -> throw CompilerException("string or array arguments should have been converted to their pointer value in the Ast $callPosition")
|
|
else -> TODO("pass parameter of type $paramDt in registers XY at $callPosition")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// carry is set last, to avoid clobbering it when loading the other parameters
|
|
when (carryParam) {
|
|
true -> prog.instr(Opcode.SEC)
|
|
false -> prog.instr(Opcode.CLC)
|
|
}
|
|
return restoreX
|
|
}
|
|
|
|
private fun translateBinaryOperator(operator: String, dt: DataType) {
|
|
if(dt !in NumericDatatypes)
|
|
throw CompilerException("non-numeric datatype for operator: $dt")
|
|
val opcode = when(operator) {
|
|
"+" -> {
|
|
when(dt) {
|
|
DataType.UBYTE -> Opcode.ADD_UB
|
|
DataType.BYTE -> Opcode.ADD_B
|
|
DataType.UWORD -> Opcode.ADD_UW
|
|
DataType.WORD -> Opcode.ADD_W
|
|
DataType.FLOAT -> Opcode.ADD_F
|
|
else -> throw CompilerException("only byte/word/float possible")
|
|
}
|
|
}
|
|
"-" -> {
|
|
when(dt) {
|
|
DataType.UBYTE -> Opcode.SUB_UB
|
|
DataType.BYTE -> Opcode.SUB_B
|
|
DataType.UWORD -> Opcode.SUB_UW
|
|
DataType.WORD -> Opcode.SUB_W
|
|
DataType.FLOAT -> Opcode.SUB_F
|
|
else -> throw CompilerException("only byte/word/float possible")
|
|
}
|
|
}
|
|
"*" -> {
|
|
when(dt) {
|
|
DataType.UBYTE -> Opcode.MUL_UB
|
|
DataType.BYTE -> Opcode.MUL_B
|
|
DataType.UWORD -> Opcode.MUL_UW
|
|
DataType.WORD -> Opcode.MUL_W
|
|
DataType.FLOAT -> Opcode.MUL_F
|
|
else -> throw CompilerException("only byte/word/float possible")
|
|
}
|
|
}
|
|
"/" -> {
|
|
when(dt) {
|
|
DataType.UBYTE -> Opcode.IDIV_UB
|
|
DataType.BYTE -> Opcode.IDIV_B
|
|
DataType.UWORD -> Opcode.IDIV_UW
|
|
DataType.WORD -> Opcode.IDIV_W
|
|
DataType.FLOAT -> Opcode.DIV_F
|
|
else -> throw CompilerException("only byte/word/float possible")
|
|
}
|
|
}
|
|
"%" -> {
|
|
when(dt) {
|
|
DataType.UBYTE -> Opcode.REMAINDER_UB
|
|
DataType.UWORD -> Opcode.REMAINDER_UW
|
|
DataType.BYTE, DataType.WORD -> throw CompilerException("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
|
else -> throw CompilerException("only byte/word operands possible")
|
|
}
|
|
}
|
|
"**" -> {
|
|
when(dt) {
|
|
in IntegerDatatypes -> throw CompilerException("power operator requires floating points")
|
|
DataType.FLOAT -> Opcode.POW_F
|
|
else -> throw CompilerException("only numeric datatype possible")
|
|
}
|
|
}
|
|
"&" -> {
|
|
when(dt) {
|
|
in ByteDatatypes -> Opcode.BITAND_BYTE
|
|
in WordDatatypes -> Opcode.BITAND_WORD
|
|
else -> throw CompilerException("only byte/word possible")
|
|
}
|
|
}
|
|
"|" -> {
|
|
when(dt) {
|
|
in ByteDatatypes -> Opcode.BITOR_BYTE
|
|
in WordDatatypes -> Opcode.BITOR_WORD
|
|
else -> throw CompilerException("only byte/word possible")
|
|
}
|
|
}
|
|
"^" -> {
|
|
when(dt) {
|
|
in ByteDatatypes -> Opcode.BITXOR_BYTE
|
|
in WordDatatypes -> Opcode.BITXOR_WORD
|
|
else -> throw CompilerException("only byte/word possible")
|
|
}
|
|
}
|
|
"and" -> {
|
|
when(dt) {
|
|
in ByteDatatypes -> Opcode.AND_BYTE
|
|
in WordDatatypes -> Opcode.AND_WORD
|
|
else -> throw CompilerException("only byte/word possible")
|
|
}
|
|
}
|
|
"or" -> {
|
|
when(dt) {
|
|
in ByteDatatypes -> Opcode.OR_BYTE
|
|
in WordDatatypes -> Opcode.OR_WORD
|
|
else -> throw CompilerException("only byte/word possible")
|
|
}
|
|
}
|
|
"xor" -> {
|
|
when(dt) {
|
|
in ByteDatatypes -> Opcode.XOR_BYTE
|
|
in WordDatatypes -> Opcode.XOR_WORD
|
|
else -> throw CompilerException("only byte/word possible")
|
|
}
|
|
}
|
|
"<" -> {
|
|
when(dt) {
|
|
DataType.UBYTE -> Opcode.LESS_UB
|
|
DataType.BYTE -> Opcode.LESS_B
|
|
DataType.UWORD -> Opcode.LESS_UW
|
|
DataType.WORD -> Opcode.LESS_W
|
|
DataType.FLOAT -> Opcode.LESS_F
|
|
else -> throw CompilerException("only byte/word/lfoat possible")
|
|
}
|
|
}
|
|
">" -> {
|
|
when(dt) {
|
|
DataType.UBYTE -> Opcode.GREATER_UB
|
|
DataType.BYTE -> Opcode.GREATER_B
|
|
DataType.UWORD -> Opcode.GREATER_UW
|
|
DataType.WORD -> Opcode.GREATER_W
|
|
DataType.FLOAT -> Opcode.GREATER_F
|
|
else -> throw CompilerException("only byte/word/lfoat possible")
|
|
}
|
|
}
|
|
"<=" -> {
|
|
when(dt) {
|
|
DataType.UBYTE -> Opcode.LESSEQ_UB
|
|
DataType.BYTE -> Opcode.LESSEQ_B
|
|
DataType.UWORD -> Opcode.LESSEQ_UW
|
|
DataType.WORD -> Opcode.LESSEQ_W
|
|
DataType.FLOAT -> Opcode.LESSEQ_F
|
|
else -> throw CompilerException("only byte/word/lfoat possible")
|
|
}
|
|
}
|
|
">=" -> {
|
|
when(dt) {
|
|
DataType.UBYTE -> Opcode.GREATEREQ_UB
|
|
DataType.BYTE -> Opcode.GREATEREQ_B
|
|
DataType.UWORD -> Opcode.GREATEREQ_UW
|
|
DataType.WORD -> Opcode.GREATEREQ_W
|
|
DataType.FLOAT -> Opcode.GREATEREQ_F
|
|
else -> throw CompilerException("only byte/word/lfoat possible")
|
|
}
|
|
}
|
|
"==" -> {
|
|
when (dt) {
|
|
in ByteDatatypes -> Opcode.EQUAL_BYTE
|
|
in WordDatatypes -> Opcode.EQUAL_WORD
|
|
DataType.FLOAT -> Opcode.EQUAL_F
|
|
else -> throw CompilerException("only byte/word/lfoat possible")
|
|
}
|
|
}
|
|
"!=" -> {
|
|
when (dt) {
|
|
in ByteDatatypes -> Opcode.NOTEQUAL_BYTE
|
|
in WordDatatypes -> Opcode.NOTEQUAL_WORD
|
|
DataType.FLOAT -> Opcode.NOTEQUAL_F
|
|
else -> throw CompilerException("only byte/word/lfoat possible")
|
|
}
|
|
}
|
|
else -> throw FatalAstException("const evaluation for invalid operator $operator")
|
|
}
|
|
prog.instr(opcode)
|
|
}
|
|
|
|
private fun translateBitshiftedOperator(operator: String, leftDt: DataType, amount: LiteralValue?) {
|
|
if(amount?.asIntegerValue == null)
|
|
throw FatalAstException("bitshift operators should only have constant integer value as right operand")
|
|
var shifts=amount.asIntegerValue
|
|
if(shifts<0)
|
|
throw FatalAstException("bitshift value should be >= 0")
|
|
|
|
prog.removeLastInstruction() // the amount of shifts is not used as a stack value
|
|
if(shifts==0)
|
|
return
|
|
while(shifts>0) {
|
|
if(operator==">>") {
|
|
when (leftDt) {
|
|
DataType.UBYTE -> prog.instr(Opcode.SHIFTEDR_UBYTE)
|
|
DataType.BYTE -> prog.instr(Opcode.SHIFTEDR_SBYTE)
|
|
DataType.UWORD -> prog.instr(Opcode.SHIFTEDR_UWORD)
|
|
DataType.WORD -> prog.instr(Opcode.SHIFTEDR_SWORD)
|
|
else -> throw CompilerException("wrong datatype")
|
|
}
|
|
} else if(operator=="<<") {
|
|
when (leftDt) {
|
|
in ByteDatatypes -> prog.instr(Opcode.SHIFTEDL_BYTE)
|
|
in WordDatatypes -> prog.instr(Opcode.SHIFTEDL_WORD)
|
|
else -> throw CompilerException("wrong datatype")
|
|
}
|
|
}
|
|
shifts--
|
|
}
|
|
}
|
|
|
|
private fun translatePrefixOperator(operator: String, operandDt: DataType?) {
|
|
if(operandDt==null)
|
|
throw CompilerException("operand datatype not known")
|
|
val opcode = when(operator) {
|
|
"+" -> Opcode.NOP
|
|
"-" -> {
|
|
when (operandDt) {
|
|
DataType.BYTE -> Opcode.NEG_B
|
|
DataType.WORD -> Opcode.NEG_W
|
|
DataType.FLOAT -> Opcode.NEG_F
|
|
else -> throw CompilerException("only byte/word/foat possible")
|
|
}
|
|
}
|
|
"~" -> {
|
|
when(operandDt) {
|
|
in ByteDatatypes -> Opcode.INV_BYTE
|
|
in WordDatatypes -> Opcode.INV_WORD
|
|
else -> throw CompilerException("only byte/word possible")
|
|
}
|
|
}
|
|
"not" -> {
|
|
when(operandDt) {
|
|
in ByteDatatypes -> Opcode.NOT_BYTE
|
|
in WordDatatypes -> Opcode.NOT_WORD
|
|
else -> throw CompilerException("only byte/word possible")
|
|
}
|
|
}
|
|
else -> throw FatalAstException("const evaluation for invalid prefix operator $operator")
|
|
}
|
|
prog.instr(opcode)
|
|
}
|
|
|
|
private fun translate(arrayindexed: ArrayIndexedExpression, write: Boolean) {
|
|
val variable = arrayindexed.identifier.targetVarDecl(program.namespace)!!
|
|
translate(arrayindexed.arrayspec.index)
|
|
if (write)
|
|
prog.instr(opcodeWriteindexedvar(variable.datatype), callLabel = variable.scopedname)
|
|
else
|
|
prog.instr(opcodeReadindexedvar(variable.datatype), callLabel = variable.scopedname)
|
|
}
|
|
|
|
private fun createSyscall(funcname: String) {
|
|
val function = (
|
|
if (funcname.startsWith("vm_"))
|
|
funcname
|
|
else
|
|
"FUNC_$funcname"
|
|
).toUpperCase()
|
|
val callNr = Syscall.valueOf(function).callNr
|
|
prog.instr(Opcode.SYSCALL, RuntimeValue(DataType.UBYTE, callNr))
|
|
}
|
|
|
|
private fun translate(stmt: Jump, branchOpcode: Opcode?) {
|
|
var jumpAddress: RuntimeValue? = null
|
|
var jumpLabel: String? = null
|
|
|
|
when {
|
|
stmt.generatedLabel!=null -> jumpLabel = stmt.generatedLabel
|
|
stmt.address!=null -> {
|
|
if(branchOpcode in branchOpcodes)
|
|
throw CompilerException("cannot branch to address, should use absolute jump instead")
|
|
jumpAddress = RuntimeValue(DataType.UWORD, stmt.address)
|
|
}
|
|
else -> {
|
|
val target = stmt.identifier!!.targetStatement(program.namespace)!!
|
|
jumpLabel = when(target) {
|
|
is Label -> target.scopedname
|
|
is Subroutine -> target.scopedname
|
|
else -> throw CompilerException("invalid jump target type ${target::class}")
|
|
}
|
|
}
|
|
}
|
|
prog.line(stmt.position)
|
|
prog.instr(branchOpcode ?: Opcode.JUMP, jumpAddress, callLabel = jumpLabel)
|
|
}
|
|
|
|
private fun translate(stmt: PostIncrDecr) {
|
|
prog.line(stmt.position)
|
|
when {
|
|
stmt.target.register != null -> when(stmt.operator) {
|
|
"++" -> prog.instr(Opcode.INC_VAR_UB, callLabel = stmt.target.register!!.name)
|
|
"--" -> prog.instr(Opcode.DEC_VAR_UB, callLabel = stmt.target.register!!.name)
|
|
}
|
|
stmt.target.identifier != null -> {
|
|
val targetStatement = stmt.target.identifier!!.targetVarDecl(program.namespace)!!
|
|
when(stmt.operator) {
|
|
"++" -> prog.instr(opcodeIncvar(targetStatement.datatype), callLabel = targetStatement.scopedname)
|
|
"--" -> prog.instr(opcodeDecvar(targetStatement.datatype), callLabel = targetStatement.scopedname)
|
|
}
|
|
}
|
|
stmt.target.arrayindexed != null -> {
|
|
val variable = stmt.target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!
|
|
translate(stmt.target.arrayindexed!!.arrayspec.index)
|
|
when(stmt.operator) {
|
|
"++" -> prog.instr(opcodeIncArrayindexedVar(variable.datatype), callLabel = variable.scopedname)
|
|
"--" -> prog.instr(opcodeDecArrayindexedVar(variable.datatype), callLabel = variable.scopedname)
|
|
}
|
|
}
|
|
stmt.target.memoryAddress != null -> {
|
|
val address = stmt.target.memoryAddress?.addressExpression?.constValue(program)?.asIntegerValue
|
|
if(address!=null) {
|
|
when(stmt.operator) {
|
|
"++" -> prog.instr(Opcode.INC_MEMORY, RuntimeValue(DataType.UWORD, address))
|
|
"--" -> prog.instr(Opcode.DEC_MEMORY, RuntimeValue(DataType.UWORD, address))
|
|
}
|
|
} else {
|
|
translate(stmt.target.memoryAddress!!.addressExpression)
|
|
when(stmt.operator) {
|
|
"++" -> prog.instr(Opcode.POP_INC_MEMORY)
|
|
"--" -> prog.instr(Opcode.POP_DEC_MEMORY)
|
|
}
|
|
}
|
|
}
|
|
else -> throw CompilerException("very strange postincrdecr ${stmt.target}")
|
|
}
|
|
}
|
|
|
|
private fun translate(stmt: Assignment) {
|
|
prog.line(stmt.position)
|
|
translate(stmt.value)
|
|
|
|
val assignTarget= stmt.singleTarget
|
|
if(assignTarget==null) {
|
|
// we're dealing with multiple return values
|
|
translateMultiReturnAssignment(stmt)
|
|
return
|
|
}
|
|
|
|
val valueDt = stmt.value.inferType(program)
|
|
val targetDt = assignTarget.inferType(program, stmt)
|
|
if(valueDt!=targetDt) {
|
|
// convert value to target datatype if possible
|
|
// @todo use convertType()????
|
|
when(targetDt) {
|
|
in ByteDatatypes ->
|
|
if(valueDt!= DataType.BYTE && valueDt!= DataType.UBYTE)
|
|
throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
|
|
DataType.WORD -> {
|
|
when (valueDt) {
|
|
DataType.UBYTE -> prog.instr(Opcode.CAST_UB_TO_W)
|
|
DataType.BYTE -> prog.instr(Opcode.CAST_B_TO_W)
|
|
else -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
|
|
}
|
|
}
|
|
DataType.UWORD -> {
|
|
when (valueDt) {
|
|
DataType.UBYTE -> prog.instr(Opcode.CAST_UB_TO_UW)
|
|
DataType.BYTE -> prog.instr(Opcode.CAST_B_TO_UW)
|
|
DataType.STR, DataType.STR_S -> pushHeapVarAddress(stmt.value, true)
|
|
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.ARRAY_W, DataType.ARRAY_UW, DataType.ARRAY_F -> {
|
|
if (stmt.value is IdentifierReference) {
|
|
val vardecl = (stmt.value as IdentifierReference).targetVarDecl(program.namespace)!!
|
|
prog.removeLastInstruction()
|
|
prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = vardecl.scopedname)
|
|
}
|
|
else
|
|
throw CompilerException("can only take address of a literal string value or a string/array variable")
|
|
}
|
|
else -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
|
|
}
|
|
}
|
|
DataType.FLOAT -> {
|
|
when (valueDt) {
|
|
DataType.UBYTE -> prog.instr(Opcode.CAST_UB_TO_F)
|
|
DataType.BYTE -> prog.instr(Opcode.CAST_B_TO_F)
|
|
DataType.UWORD -> prog.instr(Opcode.CAST_UW_TO_F)
|
|
DataType.WORD -> prog.instr(Opcode.CAST_W_TO_F)
|
|
else -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
|
|
}
|
|
}
|
|
in StringDatatypes -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
|
|
in ArrayDatatypes -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
|
|
else -> throw CompilerException("weird/unknown targetdt")
|
|
}
|
|
}
|
|
|
|
if(stmt.aug_op!=null)
|
|
throw CompilerException("augmented assignment should have been converted to regular assignment already")
|
|
|
|
// pop the result value back into the assignment target
|
|
val datatype = assignTarget.inferType(program, stmt)!!
|
|
popValueIntoTarget(assignTarget, datatype)
|
|
}
|
|
|
|
private fun pushHeapVarAddress(value: IExpression, removeLastOpcode: Boolean) {
|
|
when (value) {
|
|
is LiteralValue -> throw CompilerException("can only push address of string or array (value on the heap)")
|
|
is IdentifierReference -> {
|
|
val vardecl = value.targetVarDecl(program.namespace)!!
|
|
if(removeLastOpcode) prog.removeLastInstruction()
|
|
prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = vardecl.scopedname)
|
|
}
|
|
else -> throw CompilerException("can only take address of a literal string value or a string/array variable")
|
|
}
|
|
}
|
|
|
|
private fun pushFloatAddress(value: IExpression) {
|
|
when (value) {
|
|
is LiteralValue -> throw CompilerException("can only push address of float that is a variable on the heap")
|
|
is IdentifierReference -> {
|
|
val vardecl = value.targetVarDecl(program.namespace)!!
|
|
prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = vardecl.scopedname)
|
|
}
|
|
else -> throw CompilerException("can only take address of a the float as constant literal or variable")
|
|
}
|
|
}
|
|
|
|
private fun translateMultiReturnAssignment(stmt: Assignment) {
|
|
val targetStmt = (stmt.value as? FunctionCall)?.target?.targetStatement(program.namespace)
|
|
if(targetStmt is Subroutine && targetStmt.isAsmSubroutine) {
|
|
// this is the only case where multiple assignment targets are allowed: a call to an asmsub with multiple return values
|
|
// the return values are already on the stack (the subroutine call puts them there)
|
|
if(stmt.targets.size!=targetStmt.asmReturnvaluesRegisters.size)
|
|
throw CompilerException("asmsub number of return values doesn't match number of assignment targets ${stmt.position}")
|
|
for(target in stmt.targets) {
|
|
val dt = target.inferType(program, stmt)
|
|
popValueIntoTarget(target, dt!!)
|
|
}
|
|
} else throw CompilerException("can only use multiple assignment targets on an asmsub call")
|
|
}
|
|
|
|
private fun popValueIntoTarget(assignTarget: AssignTarget, datatype: DataType) {
|
|
when {
|
|
assignTarget.identifier != null -> {
|
|
val target = assignTarget.identifier.targetStatement(program.namespace)!!
|
|
if (target is VarDecl) {
|
|
when (target.type) {
|
|
VarDeclType.VAR -> {
|
|
val opcode = opcodePopvar(datatype)
|
|
prog.instr(opcode, callLabel = target.scopedname)
|
|
}
|
|
VarDeclType.MEMORY -> {
|
|
val opcode = opcodePopmem(datatype)
|
|
val address = target.value?.constValue(program)!!.asIntegerValue!!
|
|
prog.instr(opcode, RuntimeValue(DataType.UWORD, address))
|
|
}
|
|
VarDeclType.CONST -> throw CompilerException("cannot assign to const")
|
|
}
|
|
} else throw CompilerException("invalid assignment target type ${target::class}")
|
|
}
|
|
assignTarget.register != null -> prog.instr(Opcode.POP_VAR_BYTE, callLabel = assignTarget.register.name)
|
|
assignTarget.arrayindexed != null -> translate(assignTarget.arrayindexed, true) // write value to it
|
|
assignTarget.memoryAddress != null -> {
|
|
val address = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.asIntegerValue
|
|
if(address!=null) {
|
|
// const integer address given
|
|
prog.instr(Opcode.POP_MEM_BYTE, arg= RuntimeValue(DataType.UWORD, address))
|
|
} else {
|
|
translate(assignTarget.memoryAddress!!)
|
|
}
|
|
}
|
|
else -> throw CompilerException("corrupt assigntarget $assignTarget")
|
|
}
|
|
}
|
|
|
|
private fun translate(stmt: Return) {
|
|
// put the return values on the stack, in reversed order. The caller will process them.
|
|
for(value in stmt.values.reversed()) {
|
|
translate(value)
|
|
}
|
|
prog.line(stmt.position)
|
|
prog.instr(Opcode.RETURN)
|
|
}
|
|
|
|
private fun translate(stmt: Label) {
|
|
prog.label(stmt.scopedname)
|
|
}
|
|
|
|
private fun translate(loop: ForLoop) {
|
|
if(loop.body.containsNoCodeNorVars()) return
|
|
prog.line(loop.position)
|
|
val loopVarName: String
|
|
val loopVarDt: DataType
|
|
|
|
if(loop.loopRegister!=null) {
|
|
val reg = loop.loopRegister
|
|
loopVarName = reg.name
|
|
loopVarDt = DataType.UBYTE
|
|
} else {
|
|
val loopvar = loop.loopVar!!.targetVarDecl(program.namespace)!!
|
|
loopVarName = loopvar.scopedname
|
|
loopVarDt = loopvar.datatype
|
|
}
|
|
|
|
if(loop.iterable is RangeExpr) {
|
|
val range = (loop.iterable as RangeExpr).toConstantIntegerRange()
|
|
if(range!=null) {
|
|
// loop over a range with constant start, last and step values
|
|
if (range.isEmpty())
|
|
throw CompilerException("loop over empty range should have been optimized away")
|
|
else if (range.count()==1)
|
|
throw CompilerException("loop over just 1 value should have been optimized away")
|
|
if((range.last-range.first) % range.step != 0)
|
|
throw CompilerException("range first and last must be exactly inclusive")
|
|
when (loopVarDt) {
|
|
DataType.UBYTE -> {
|
|
if (range.first < 0 || range.first > 255 || range.last < 0 || range.last > 255)
|
|
throw CompilerException("range out of bounds for ubyte")
|
|
}
|
|
DataType.UWORD -> {
|
|
if (range.first < 0 || range.first > 65535 || range.last < 0 || range.last > 65535)
|
|
throw CompilerException("range out of bounds for uword")
|
|
}
|
|
DataType.BYTE -> {
|
|
if (range.first < -128 || range.first > 127 || range.last < -128 || range.last > 127)
|
|
throw CompilerException("range out of bounds for byte")
|
|
}
|
|
DataType.WORD -> {
|
|
if (range.first < -32768 || range.first > 32767 || range.last < -32768 || range.last > 32767)
|
|
throw CompilerException("range out of bounds for word")
|
|
}
|
|
else -> throw CompilerException("range must be byte or word")
|
|
}
|
|
translateForOverConstantRange(loopVarName, loopVarDt, range, loop.body)
|
|
} else {
|
|
// loop over a range where one or more of the start, last or step values is not a constant
|
|
if(loop.loopRegister!=null) {
|
|
translateForOverVariableRange(null, loop.loopRegister, loop.iterable as RangeExpr, loop.body)
|
|
}
|
|
else {
|
|
translateForOverVariableRange(loop.loopVar!!.nameInSource, null, loop.iterable as RangeExpr, loop.body)
|
|
}
|
|
}
|
|
} else {
|
|
// ok, must be a literalvalue
|
|
when {
|
|
loop.iterable is IdentifierReference -> {
|
|
val idRef = loop.iterable as IdentifierReference
|
|
val vardecl = idRef.targetVarDecl(program.namespace)!!
|
|
val iterableValue = vardecl.value as LiteralValue
|
|
if(iterableValue.type !in IterableDatatypes)
|
|
throw CompilerException("loop over something that isn't iterable ${loop.iterable}")
|
|
translateForOverIterableVar(loop, loopVarDt, iterableValue)
|
|
}
|
|
loop.iterable is LiteralValue -> throw CompilerException("literal value in loop must have been moved to heap already $loop")
|
|
else -> throw CompilerException("loopvar is something strange ${loop.iterable}")
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun translateForOverIterableVar(loop: ForLoop, loopvarDt: DataType, iterableValue: LiteralValue) {
|
|
if(loopvarDt== DataType.UBYTE && iterableValue.type !in setOf(DataType.STR, DataType.STR_S, DataType.ARRAY_UB))
|
|
throw CompilerException("loop variable type doesn't match iterableValue type")
|
|
else if(loopvarDt== DataType.UWORD && iterableValue.type != DataType.ARRAY_UW)
|
|
throw CompilerException("loop variable type doesn't match iterableValue type")
|
|
else if(loopvarDt== DataType.FLOAT && iterableValue.type != DataType.ARRAY_F)
|
|
throw CompilerException("loop variable type doesn't match iterableValue type")
|
|
|
|
val numElements: Int
|
|
when(iterableValue.type) {
|
|
!in IterableDatatypes -> throw CompilerException("non-iterableValue type")
|
|
DataType.STR, DataType.STR_S -> {
|
|
numElements = iterableValue.strvalue!!.length
|
|
if(numElements>255) throw CompilerException("string length > 255")
|
|
}
|
|
DataType.ARRAY_UB, DataType.ARRAY_B,
|
|
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
|
numElements = iterableValue.arrayvalue?.size ?: program.heap.get(iterableValue.heapId!!).arraysize
|
|
if(numElements>255) throw CompilerException("string length > 255")
|
|
}
|
|
DataType.ARRAY_F -> {
|
|
numElements = iterableValue.arrayvalue?.size ?: program.heap.get(iterableValue.heapId!!).arraysize
|
|
if(numElements>255) throw CompilerException("string length > 255")
|
|
}
|
|
else -> throw CompilerException("weird datatype")
|
|
}
|
|
|
|
if(loop.loopRegister!=null && loop.loopRegister== Register.X)
|
|
throw CompilerException("loopVar cannot use X register because that is used as internal stack pointer")
|
|
|
|
/**
|
|
* indexVar = 0
|
|
* loop:
|
|
* LV = iterableValue[indexVar]
|
|
* ..body..
|
|
* ..break statement: goto break
|
|
* ..continue statement: goto continue
|
|
* ..
|
|
* continue:
|
|
* indexVar++
|
|
* if indexVar!=numElements goto loop
|
|
* break:
|
|
* nop
|
|
*/
|
|
val loopLabel = makeLabel(loop, "loop")
|
|
val continueLabel = makeLabel(loop, "continue")
|
|
val breakLabel = makeLabel(loop, "break")
|
|
val indexVarType = if (numElements <= 255) DataType.UBYTE else DataType.UWORD
|
|
val indexVar = loop.body.getLabelOrVariable(ForLoop.iteratorLoopcounterVarname) as VarDecl
|
|
|
|
continueStmtLabelStack.push(continueLabel)
|
|
breakStmtLabelStack.push(breakLabel)
|
|
|
|
// set the index var to zero before the loop
|
|
prog.instr(opcodePush(indexVarType), RuntimeValue(indexVarType, 0))
|
|
prog.instr(opcodePopvar(indexVarType), callLabel = indexVar.scopedname)
|
|
|
|
// loop starts here
|
|
prog.label(loopLabel)
|
|
val assignTarget = if(loop.loopRegister!=null)
|
|
AssignTarget(loop.loopRegister, null, null, null, loop.position)
|
|
else
|
|
AssignTarget(null, loop.loopVar!!.copy(), null, null, loop.position)
|
|
val arrayspec = ArrayIndex(IdentifierReference(listOf(ForLoop.iteratorLoopcounterVarname), loop.position), loop.position)
|
|
val assignLv = Assignment(
|
|
listOf(assignTarget), null,
|
|
ArrayIndexedExpression((loop.iterable as IdentifierReference).copy(), arrayspec, loop.position),
|
|
loop.position)
|
|
assignLv.linkParents(loop.body)
|
|
translate(assignLv)
|
|
translate(loop.body)
|
|
prog.label(continueLabel)
|
|
|
|
prog.instr(opcodeIncvar(indexVarType), callLabel = indexVar.scopedname)
|
|
prog.instr(opcodePushvar(indexVarType), callLabel = indexVar.scopedname)
|
|
prog.instr(opcodeCompare(indexVarType), RuntimeValue(indexVarType, numElements))
|
|
prog.instr(Opcode.BNZ, callLabel = loopLabel)
|
|
|
|
prog.label(breakLabel)
|
|
prog.instr(Opcode.NOP)
|
|
|
|
breakStmtLabelStack.pop()
|
|
continueStmtLabelStack.pop()
|
|
}
|
|
|
|
private fun translateForOverConstantRange(varname: String, varDt: DataType, range: IntProgression, body: AnonymousScope) {
|
|
/**
|
|
* for LV in start..last { body }
|
|
* (and we already know that the range is not empty, and first and last are exactly inclusive.)
|
|
* (also we know that the range's last value is really the exact last occurring value of the range)
|
|
* (and finally, start and last are constant integer values)
|
|
* ->
|
|
* LV = start
|
|
* loop:
|
|
* ..body..
|
|
* ..break statement: goto break
|
|
* ..continue statement: goto continue
|
|
* ..
|
|
* continue:
|
|
* LV++ (if step=1) / LV += step (if step > 1)
|
|
* LV-- (if step=-1) / LV -= abs(step) (if step < 1)
|
|
* if LV!=(last+step) goto loop
|
|
* break:
|
|
* nop
|
|
*/
|
|
val loopLabel = makeLabel(body, "loop")
|
|
val continueLabel = makeLabel(body, "continue")
|
|
val breakLabel = makeLabel(body, "break")
|
|
|
|
continueStmtLabelStack.push(continueLabel)
|
|
breakStmtLabelStack.push(breakLabel)
|
|
|
|
prog.instr(opcodePush(varDt), RuntimeValue(varDt, range.first))
|
|
prog.instr(opcodePopvar(varDt), callLabel = varname)
|
|
prog.label(loopLabel)
|
|
translate(body)
|
|
prog.label(continueLabel)
|
|
val numberOfIncDecsForOptimize = 8
|
|
when {
|
|
range.step in 1..numberOfIncDecsForOptimize -> {
|
|
repeat(range.step) {
|
|
prog.instr(opcodeIncvar(varDt), callLabel = varname)
|
|
}
|
|
}
|
|
range.step in -1 downTo -numberOfIncDecsForOptimize -> {
|
|
repeat(abs(range.step)) {
|
|
prog.instr(opcodeDecvar(varDt), callLabel = varname)
|
|
}
|
|
}
|
|
range.step>numberOfIncDecsForOptimize -> {
|
|
prog.instr(opcodePushvar(varDt), callLabel = varname)
|
|
prog.instr(opcodePush(varDt), RuntimeValue(varDt, range.step))
|
|
prog.instr(opcodeAdd(varDt))
|
|
prog.instr(opcodePopvar(varDt), callLabel = varname)
|
|
}
|
|
range.step<numberOfIncDecsForOptimize -> {
|
|
prog.instr(opcodePushvar(varDt), callLabel = varname)
|
|
prog.instr(opcodePush(varDt), RuntimeValue(varDt, abs(range.step)))
|
|
prog.instr(opcodeSub(varDt))
|
|
prog.instr(opcodePopvar(varDt), callLabel = varname)
|
|
}
|
|
}
|
|
|
|
if(range.last==0) {
|
|
// optimize for the for loop that counts to 0
|
|
prog.instr(if(range.first>0) Opcode.BPOS else Opcode.BNEG, callLabel = loopLabel)
|
|
} else {
|
|
prog.instr(opcodePushvar(varDt), callLabel = varname)
|
|
val checkValue =
|
|
when (varDt) {
|
|
DataType.UBYTE -> (range.last + range.step) and 255
|
|
DataType.UWORD -> (range.last + range.step) and 65535
|
|
DataType.BYTE, DataType.WORD -> range.last + range.step
|
|
else -> throw CompilerException("invalid loop var dt $varDt")
|
|
}
|
|
prog.instr(opcodeCompare(varDt), RuntimeValue(varDt, checkValue))
|
|
prog.instr(Opcode.BNZ, callLabel = loopLabel)
|
|
}
|
|
prog.label(breakLabel)
|
|
prog.instr(Opcode.NOP)
|
|
// note: ending value of loop register / variable is *undefined* after this point!
|
|
|
|
breakStmtLabelStack.pop()
|
|
continueStmtLabelStack.pop()
|
|
}
|
|
|
|
private fun translateForOverVariableRange(varname: List<String>?, register: Register?,
|
|
range: RangeExpr, body: AnonymousScope) {
|
|
/*
|
|
* for LV in start..last { body }
|
|
* (where at least one of the start, last, step values is not a constant)
|
|
* (so we can't make any static assumptions about them)
|
|
* ->
|
|
* LV = start
|
|
* loop:
|
|
* if (step > 0) {
|
|
* if(LV>last) goto break
|
|
* } else {
|
|
* if(LV<last) goto break
|
|
* }
|
|
* ..body..
|
|
* ..break statement: goto break
|
|
* ..continue statement: goto continue
|
|
* ..
|
|
* continue:
|
|
*
|
|
* (if we know step is a constant:)
|
|
* step == 1 ->
|
|
* LV++
|
|
* if_nz goto loop ;; acts as overflow check
|
|
* step == -1 ->
|
|
* LV--
|
|
* @todo some condition to check for not overflow , jump to loop
|
|
* (not constant or other step:
|
|
* LV += step ; @todo implement overflow on the appropriate arithmetic operations
|
|
* if_vc goto loop ;; not overflowed
|
|
* break:
|
|
* nop
|
|
*/
|
|
fun makeAssignmentTarget(): AssignTarget {
|
|
return if(varname!=null)
|
|
AssignTarget(null, IdentifierReference(varname, range.position), null, null, range.position)
|
|
else
|
|
AssignTarget(register, null, null, null, range.position)
|
|
}
|
|
|
|
val startAssignment = Assignment(listOf(makeAssignmentTarget()), null, range.from, range.position)
|
|
startAssignment.linkParents(body)
|
|
translate(startAssignment)
|
|
|
|
val loopLabel = makeLabel(body, "loop")
|
|
val continueLabel = makeLabel(body, "continue")
|
|
val breakLabel = makeLabel(body, "break")
|
|
val literalStepValue = (range.step as? LiteralValue)?.asNumericValue?.toInt()
|
|
|
|
continueStmtLabelStack.push(continueLabel)
|
|
breakStmtLabelStack.push(breakLabel)
|
|
|
|
prog.label(loopLabel)
|
|
if(literalStepValue!=null) {
|
|
// Step is a constant. We can optimize some stuff!
|
|
val loopVar =
|
|
if(varname!=null)
|
|
IdentifierReference(varname, range.position)
|
|
else
|
|
RegisterExpr(register!!, range.position)
|
|
|
|
val condition =
|
|
if(literalStepValue > 0) {
|
|
// if LV > last goto break
|
|
BinaryExpression(loopVar, ">", range.to, range.position)
|
|
} else {
|
|
// if LV < last goto break
|
|
BinaryExpression(loopVar, "<", range.to, range.position)
|
|
}
|
|
val ifstmt = IfStatement(condition,
|
|
AnonymousScope(mutableListOf(Jump(null, null, breakLabel, range.position)), range.position),
|
|
AnonymousScope(mutableListOf(), range.position),
|
|
range.position)
|
|
ifstmt.linkParents(body)
|
|
translate(ifstmt)
|
|
} else {
|
|
// Step is a variable. We can't optimize anything...
|
|
TODO("for loop with non-constant step comparison of LV, at: ${range.position}")
|
|
}
|
|
|
|
translate(body)
|
|
prog.label(continueLabel)
|
|
val lvTarget = makeAssignmentTarget()
|
|
lvTarget.linkParents(body)
|
|
val targetStatement: VarDecl? =
|
|
if(lvTarget.identifier!=null) {
|
|
lvTarget.identifier.targetVarDecl(program.namespace)
|
|
} else {
|
|
null
|
|
}
|
|
// todo deal with target.arrayindexed?
|
|
|
|
fun createLoopCode(step: Int) {
|
|
if(step!=1 && step !=-1)
|
|
TODO("can't generate code for step other than 1 or -1 right now")
|
|
|
|
// LV++ / LV--
|
|
val postIncr = PostIncrDecr(lvTarget, if (step == 1) "++" else "--", range.position)
|
|
postIncr.linkParents(body)
|
|
translate(postIncr)
|
|
if(lvTarget.register!=null)
|
|
prog.instr(Opcode.PUSH_VAR_BYTE, callLabel =lvTarget.register.name)
|
|
else {
|
|
val opcode = opcodePushvar(targetStatement!!.datatype)
|
|
prog.instr(opcode, callLabel = targetStatement.scopedname)
|
|
}
|
|
// TODO: optimize this to use a compare + branch opcode somehow?
|
|
val conditionJumpOpcode = when(targetStatement!!.datatype) {
|
|
in ByteDatatypes -> Opcode.JNZ
|
|
in WordDatatypes -> Opcode.JNZW
|
|
else -> throw CompilerException("invalid loopvar datatype (expected byte or word) $lvTarget")
|
|
}
|
|
prog.instr(conditionJumpOpcode, callLabel = loopLabel)
|
|
}
|
|
|
|
when (literalStepValue) {
|
|
1 -> createLoopCode(1)
|
|
-1 -> createLoopCode(-1)
|
|
null -> TODO("variable range forloop non-literal-const step increment, At: ${range.position}")
|
|
else -> TODO("variable range forloop step increment not 1 or -1, At: ${range.position}")
|
|
}
|
|
|
|
prog.label(breakLabel)
|
|
prog.instr(Opcode.NOP)
|
|
// note: ending value of loop register / variable is *undefined* after this point!
|
|
|
|
breakStmtLabelStack.pop()
|
|
continueStmtLabelStack.pop()
|
|
}
|
|
|
|
private fun translate(scope: AnonymousScope) = translate(scope.statements)
|
|
|
|
private fun translate(stmt: WhileLoop)
|
|
{
|
|
/*
|
|
* while condition { statements... } ->
|
|
*
|
|
* goto continue
|
|
* loop:
|
|
* statements
|
|
* break -> goto break
|
|
* continue -> goto condition
|
|
* continue:
|
|
* <evaluate condition>
|
|
* jnz/jnzw loop
|
|
* break:
|
|
* nop
|
|
*/
|
|
val loopLabel = makeLabel(stmt, "loop")
|
|
val breakLabel = makeLabel(stmt, "break")
|
|
val continueLabel = makeLabel(stmt, "continue")
|
|
prog.line(stmt.position)
|
|
breakStmtLabelStack.push(breakLabel)
|
|
continueStmtLabelStack.push(continueLabel)
|
|
prog.instr(Opcode.JUMP, callLabel = continueLabel)
|
|
prog.label(loopLabel)
|
|
translate(stmt.body)
|
|
prog.label(continueLabel)
|
|
translate(stmt.condition)
|
|
val conditionJumpOpcode = when(stmt.condition.inferType(program)) {
|
|
in ByteDatatypes -> Opcode.JNZ
|
|
in WordDatatypes -> Opcode.JNZW
|
|
else -> throw CompilerException("invalid condition datatype (expected byte or word) $stmt")
|
|
}
|
|
prog.instr(conditionJumpOpcode, callLabel = loopLabel)
|
|
prog.label(breakLabel)
|
|
prog.instr(Opcode.NOP)
|
|
breakStmtLabelStack.pop()
|
|
continueStmtLabelStack.pop()
|
|
}
|
|
|
|
private fun translate(stmt: RepeatLoop)
|
|
{
|
|
/*
|
|
* repeat { statements... } until condition ->
|
|
*
|
|
* loop:
|
|
* statements
|
|
* break -> goto break
|
|
* continue -> goto condition
|
|
* condition:
|
|
* <evaluate untilCondition>
|
|
* jz/jzw goto loop
|
|
* break:
|
|
* nop
|
|
*/
|
|
val loopLabel = makeLabel(stmt, "loop")
|
|
val continueLabel = makeLabel(stmt, "continue")
|
|
val breakLabel = makeLabel(stmt, "break")
|
|
prog.line(stmt.position)
|
|
breakStmtLabelStack.push(breakLabel)
|
|
continueStmtLabelStack.push(continueLabel)
|
|
prog.label(loopLabel)
|
|
translate(stmt.body)
|
|
prog.label(continueLabel)
|
|
translate(stmt.untilCondition)
|
|
val conditionJumpOpcode = when(stmt.untilCondition.inferType(program)) {
|
|
in ByteDatatypes -> Opcode.JZ
|
|
in WordDatatypes -> Opcode.JZW
|
|
else -> throw CompilerException("invalid condition datatype (expected byte or word) $stmt")
|
|
}
|
|
prog.instr(conditionJumpOpcode, callLabel = loopLabel)
|
|
prog.label(breakLabel)
|
|
prog.instr(Opcode.NOP)
|
|
breakStmtLabelStack.pop()
|
|
continueStmtLabelStack.pop()
|
|
}
|
|
|
|
private fun translate(expr: TypecastExpression) {
|
|
translate(expr.expression)
|
|
val sourceDt = expr.expression.inferType(program) ?: throw CompilerException("don't know what type to cast")
|
|
if(sourceDt==expr.type)
|
|
return
|
|
|
|
when(expr.type) {
|
|
DataType.UBYTE -> when(sourceDt) {
|
|
DataType.UBYTE -> {}
|
|
DataType.BYTE -> prog.instr(Opcode.CAST_B_TO_UB)
|
|
DataType.UWORD-> prog.instr(Opcode.CAST_UW_TO_UB)
|
|
DataType.WORD-> prog.instr(Opcode.CAST_W_TO_UB)
|
|
DataType.FLOAT -> prog.instr(Opcode.CAST_F_TO_UB)
|
|
else -> throw CompilerException("invalid cast $sourceDt to ${expr.type} -- should be an Ast check")
|
|
}
|
|
DataType.BYTE -> when(sourceDt) {
|
|
DataType.UBYTE -> prog.instr(Opcode.CAST_UB_TO_B)
|
|
DataType.BYTE -> {}
|
|
DataType.UWORD -> prog.instr(Opcode.CAST_UW_TO_B)
|
|
DataType.WORD -> prog.instr(Opcode.CAST_W_TO_B)
|
|
DataType.FLOAT -> prog.instr(Opcode.CAST_F_TO_B)
|
|
else -> throw CompilerException("invalid cast $sourceDt to ${expr.type} -- should be an Ast check")
|
|
}
|
|
DataType.UWORD -> when(sourceDt) {
|
|
DataType.UBYTE -> prog.instr(Opcode.CAST_UB_TO_UW)
|
|
DataType.BYTE -> prog.instr(Opcode.CAST_B_TO_UW)
|
|
DataType.UWORD -> {}
|
|
DataType.WORD -> prog.instr(Opcode.CAST_W_TO_UW)
|
|
DataType.FLOAT -> prog.instr(Opcode.CAST_F_TO_UW)
|
|
else -> throw CompilerException("invalid cast $sourceDt to ${expr.type} -- should be an Ast check")
|
|
}
|
|
DataType.WORD -> when(sourceDt) {
|
|
DataType.UBYTE -> prog.instr(Opcode.CAST_UB_TO_W)
|
|
DataType.BYTE -> prog.instr(Opcode.CAST_B_TO_W)
|
|
DataType.UWORD -> prog.instr(Opcode.CAST_UW_TO_W)
|
|
DataType.WORD -> {}
|
|
DataType.FLOAT -> prog.instr(Opcode.CAST_F_TO_W)
|
|
else -> throw CompilerException("invalid cast $sourceDt to ${expr.type} -- should be an Ast check")
|
|
}
|
|
DataType.FLOAT -> when(sourceDt) {
|
|
DataType.UBYTE -> prog.instr(Opcode.CAST_UB_TO_F)
|
|
DataType.BYTE -> prog.instr(Opcode.CAST_B_TO_F)
|
|
DataType.UWORD -> prog.instr(Opcode.CAST_UW_TO_F)
|
|
DataType.WORD -> prog.instr(Opcode.CAST_W_TO_F)
|
|
DataType.FLOAT -> {}
|
|
else -> throw CompilerException("invalid cast $sourceDt to ${expr.type} -- should be an Ast check")
|
|
}
|
|
else -> throw CompilerException("invalid cast $sourceDt to ${expr.type} -- should be an Ast check")
|
|
}
|
|
}
|
|
|
|
private fun translate(memread: DirectMemoryRead) {
|
|
// for now, only a single memory location (ubyte) is read at a time.
|
|
val address = memread.addressExpression.constValue(program)?.asIntegerValue
|
|
if(address!=null) {
|
|
prog.instr(Opcode.PUSH_MEM_UB, arg = RuntimeValue(DataType.UWORD, address))
|
|
} else {
|
|
translate(memread.addressExpression)
|
|
prog.instr(Opcode.PUSH_MEMREAD)
|
|
}
|
|
}
|
|
|
|
private fun translate(memwrite: DirectMemoryWrite) {
|
|
// for now, only a single memory location (ubyte) is written at a time.
|
|
val address = memwrite.addressExpression.constValue(program)?.asIntegerValue
|
|
if(address!=null) {
|
|
prog.instr(Opcode.POP_MEM_BYTE, arg = RuntimeValue(DataType.UWORD, address))
|
|
} else {
|
|
translate(memwrite.addressExpression)
|
|
prog.instr(Opcode.POP_MEMWRITE)
|
|
}
|
|
}
|
|
|
|
private fun translate(addrof: AddressOf) {
|
|
val target = addrof.identifier.targetVarDecl(program.namespace)!!
|
|
if(target.datatype in ArrayDatatypes || target.datatype in StringDatatypes || target.datatype== DataType.FLOAT) {
|
|
pushHeapVarAddress(addrof.identifier, false)
|
|
}
|
|
else if(target.datatype== DataType.FLOAT) {
|
|
pushFloatAddress(addrof.identifier)
|
|
}
|
|
else
|
|
throw CompilerException("cannot take memory pointer $addrof")
|
|
}
|
|
|
|
private fun translateAsmInclude(args: List<DirectiveArg>, source: Path) {
|
|
val scopeprefix = if(args[1].str!!.isNotBlank()) "${args[1].str}\t.proc\n" else ""
|
|
val scopeprefixEnd = if(args[1].str!!.isNotBlank()) "\t.pend\n" else ""
|
|
val filename=args[0].str!!
|
|
val sourcecode = loadAsmIncludeFile(filename, source)
|
|
|
|
prog.instr(Opcode.INLINE_ASSEMBLY, callLabel=null, callLabel2=scopeprefix+sourcecode+scopeprefixEnd)
|
|
}
|
|
|
|
private fun translateAsmBinary(args: List<DirectiveArg>) {
|
|
val offset = if(args.size>=2) RuntimeValue(DataType.UWORD, args[1].int!!) else null
|
|
val length = if(args.size==3) RuntimeValue(DataType.UWORD, args[2].int!!) else null
|
|
val filename = args[0].str!!
|
|
// reading the actual data is not performed by the compiler but is delegated to the assembler
|
|
prog.instr(Opcode.INCLUDE_FILE, offset, length, filename)
|
|
}
|
|
|
|
}
|
|
|
|
|
|
fun loadAsmIncludeFile(filename: String, source: Path): String {
|
|
return if (filename.startsWith("library:")) {
|
|
val resource = tryGetEmbeddedResource(filename.substring(8))
|
|
?: throw IllegalArgumentException("library file '$filename' not found")
|
|
resource.bufferedReader().use { it.readText() }
|
|
} else {
|
|
// first try in the isSameAs folder as where the containing file was imported from
|
|
val sib = source.resolveSibling(filename)
|
|
if (sib.toFile().isFile)
|
|
sib.toFile().readText()
|
|
else
|
|
File(filename).readText()
|
|
}
|
|
}
|