mirror of
https://github.com/irmen/prog8.git
synced 2025-01-12 04:30:03 +00:00
restructure compiler
This commit is contained in:
parent
88a9f2d493
commit
326e5cd81a
@ -39,11 +39,10 @@ sub start() -> () {
|
||||
float time = 0.0
|
||||
_vm_gfx_clearscr(0)
|
||||
|
||||
for X in 3 to 100 step 3/3 {
|
||||
A=44
|
||||
for X in 3 to 100 {
|
||||
A=X
|
||||
continue
|
||||
break
|
||||
A=99
|
||||
}
|
||||
|
||||
loop:
|
||||
|
@ -1,13 +1,15 @@
|
||||
package prog8
|
||||
|
||||
import java.nio.file.Paths
|
||||
import prog8.ast.*
|
||||
import prog8.parser.*
|
||||
import prog8.compiler.*
|
||||
import prog8.optimizing.constantFold
|
||||
import prog8.optimizing.optimizeStatements
|
||||
import prog8.optimizing.simplifyExpressions
|
||||
import prog8.parser.ParsingFailedError
|
||||
import prog8.parser.importModule
|
||||
import java.io.File
|
||||
import java.io.PrintStream
|
||||
import java.nio.file.Paths
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
@ -74,10 +76,10 @@ fun main(args: Array<String>) {
|
||||
intermediate.optimize()
|
||||
|
||||
val stackVmFilename = intermediate.name + "_stackvm.txt"
|
||||
val stackvmFile = File(stackVmFilename).printWriter()
|
||||
intermediate.toTextLines().forEach { stackvmFile.println(it) }
|
||||
val stackvmFile = PrintStream(File(stackVmFilename), "utf-8")
|
||||
intermediate.writeAsText(stackvmFile)
|
||||
stackvmFile.close()
|
||||
println("StackVM intermediary code written to $stackVmFilename")
|
||||
println("StackVM program code written to '$stackVmFilename'")
|
||||
|
||||
// val assembly = stackvmProg.compileToAssembly()
|
||||
//
|
||||
|
@ -1,10 +1,10 @@
|
||||
package prog8.ast
|
||||
|
||||
import prog8.functions.*
|
||||
import prog8.parser.prog8Parser
|
||||
import org.antlr.v4.runtime.ParserRuleContext
|
||||
import org.antlr.v4.runtime.tree.TerminalNode
|
||||
import prog8.compiler.Petscii
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.functions.*
|
||||
import prog8.parser.prog8Parser
|
||||
import java.nio.file.Paths
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.floor
|
||||
|
@ -74,6 +74,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions:
|
||||
} else {
|
||||
val iterableDt = forLoop.iterable.resultingDatatype(namespace)
|
||||
if (forLoop.loopRegister != null) {
|
||||
printWarning("using a register as loop variable is risky (it could get clobbered in the body)", forLoop.position)
|
||||
// loop register
|
||||
when (forLoop.loopRegister) {
|
||||
Register.A, Register.X, Register.Y -> {
|
||||
|
@ -1,18 +1,12 @@
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.ast.*
|
||||
import kotlin.experimental.and
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.pow
|
||||
import kotlin.system.exitProcess
|
||||
import prog8.stackvm.*
|
||||
import java.io.PrintStream
|
||||
|
||||
|
||||
class CompilerException(message: String?) : Exception(message)
|
||||
|
||||
// 5-byte cbm MFLPT format limitations:
|
||||
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38
|
||||
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38
|
||||
|
||||
|
||||
fun Number.toHex(): String {
|
||||
// 0..15 -> "0".."15"
|
||||
@ -28,82 +22,91 @@ fun Number.toHex(): String {
|
||||
}
|
||||
|
||||
|
||||
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) {
|
||||
|
||||
companion object {
|
||||
val zero = Mflpt5(0, 0,0,0,0)
|
||||
fun fromNumber(num: Number): Mflpt5 {
|
||||
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
|
||||
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
|
||||
// and https://en.wikipedia.org/wiki/IEEE_754-1985
|
||||
class StackVmProgram(val name: String) {
|
||||
private val instructions = mutableListOf<Instruction>()
|
||||
private val variables = mutableMapOf<String, Value>()
|
||||
private val memory = mutableMapOf<Int, List<Value>>()
|
||||
private val labels = mutableMapOf<String, Instruction>()
|
||||
val numVariables: Int
|
||||
get() {return variables.size}
|
||||
val numInstructions: Int
|
||||
get() {return instructions.size}
|
||||
|
||||
val flt = num.toDouble()
|
||||
if(flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
|
||||
throw CompilerException("floating point number out of 5-byte mflpt range: $this")
|
||||
if(flt==0.0)
|
||||
return zero
|
||||
fun optimize() {
|
||||
println("\nOptimizing stackVM code...")
|
||||
|
||||
val sign = if(flt<0.0) 0x80L else 0x00L
|
||||
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
|
||||
var mantissa = flt.absoluteValue
|
||||
|
||||
// if mantissa is too large, shift right and adjust exponent
|
||||
while(mantissa >= 0x100000000) {
|
||||
mantissa /= 2.0
|
||||
exponent ++
|
||||
}
|
||||
// if mantissa is too small, shift left and adjust exponent
|
||||
while(mantissa < 0x80000000) {
|
||||
mantissa *= 2.0
|
||||
exponent --
|
||||
}
|
||||
|
||||
return when {
|
||||
exponent<0 -> zero // underflow, use zero instead
|
||||
exponent>255 -> throw CompilerException("floating point overflow: $this")
|
||||
exponent==0 -> zero
|
||||
else -> {
|
||||
val mantLong = mantissa.toLong()
|
||||
Mflpt5(
|
||||
exponent.toShort(),
|
||||
(mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(),
|
||||
(mantLong.and(0x00ff0000L) ushr 16).toShort(),
|
||||
(mantLong.and(0x0000ff00L) ushr 8).toShort(),
|
||||
(mantLong.and(0x000000ffL)).toShort())
|
||||
}
|
||||
}
|
||||
}
|
||||
this.instructions.removeIf { it.opcode==Opcode.NOP && it !is LabelInstr } // remove nops (that are not a label)
|
||||
// todo optimize stackvm code
|
||||
}
|
||||
|
||||
fun toDouble(): Double {
|
||||
if(this == zero) return 0.0
|
||||
val exp = b0 - 128
|
||||
val sign = (b1.and(0x80)) > 0
|
||||
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
|
||||
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
|
||||
return if(sign) -result else result
|
||||
fun blockvar(scopedname: String, decl: VarDecl) {
|
||||
val value = when(decl.datatype) {
|
||||
DataType.BYTE, DataType.WORD, DataType.FLOAT -> Value(decl.datatype, (decl.value as LiteralValue).asNumericValue)
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> Value(decl.datatype, null, (decl.value as LiteralValue).strvalue)
|
||||
DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> TODO("array/matrix variable values")
|
||||
}
|
||||
variables[scopedname] = value
|
||||
}
|
||||
|
||||
fun writeAsText(out: PrintStream) {
|
||||
Program(name, instructions, labels, variables, memory).print(out)
|
||||
}
|
||||
|
||||
fun instr(opcode: Opcode, arg: Value? = null, callLabel: String? = null) {
|
||||
instructions.add(Instruction(opcode, arg, callLabel))
|
||||
}
|
||||
|
||||
fun label(labelname: String) {
|
||||
val instr = LabelInstr(labelname)
|
||||
instructions.add(instr)
|
||||
labels[labelname] = instr
|
||||
}
|
||||
|
||||
fun line(position: Position) {
|
||||
instructions.add(Instruction(Opcode.LINE, Value(DataType.STR, null, "${position.line} ${position.file}")))
|
||||
}
|
||||
}
|
||||
|
||||
enum class OutputType {
|
||||
RAW,
|
||||
PRG
|
||||
}
|
||||
|
||||
enum class LauncherType {
|
||||
BASIC,
|
||||
NONE
|
||||
}
|
||||
|
||||
enum class ZeropageType {
|
||||
BASICSAFE,
|
||||
KERNALSAFE,
|
||||
FULL
|
||||
}
|
||||
|
||||
|
||||
data class CompilationOptions(val output: OutputType,
|
||||
val launcher: LauncherType,
|
||||
val zeropage: ZeropageType,
|
||||
val floats: Boolean)
|
||||
|
||||
|
||||
class Compiler(private val options: CompilationOptions) {
|
||||
fun compile(module: Module) : StackVmProgram {
|
||||
println("\nCreating stackVM code...")
|
||||
|
||||
val namespace = module.definingScope()
|
||||
|
||||
val intermediate = StackVmProgram(module.name)
|
||||
namespace.debugPrint()
|
||||
|
||||
// create the pool of all variables used in all blocks and scopes
|
||||
val varGather = VarGatherer(intermediate)
|
||||
varGather.process(module)
|
||||
println("Number of allocated variables and constants: ${intermediate.variables.size} (${intermediate.variablesMemSize} bytes)")
|
||||
println("Number of allocated variables and constants: ${intermediate.numVariables}")
|
||||
|
||||
val translator = StatementTranslator(intermediate, namespace)
|
||||
translator.process(module)
|
||||
println("Number of source statements: ${translator.stmtUniqueSequenceNr}")
|
||||
println("Number of vm instructions: ${intermediate.instructions.size}")
|
||||
println("Number of vm instructions: ${intermediate.numInstructions}")
|
||||
|
||||
return intermediate
|
||||
}
|
||||
@ -146,7 +149,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
"%asmbinary" -> throw CompilerException("can't use %asmbinary in stackvm")
|
||||
"%breakpoint" -> {
|
||||
stackvmProg.line(directive.position)
|
||||
stackvmProg.instruction("break")
|
||||
stackvmProg.instr(Opcode.BREAKPOINT)
|
||||
}
|
||||
}
|
||||
return super.process(directive)
|
||||
@ -178,12 +181,12 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
|
||||
private fun translate(stmt: Continue) {
|
||||
stackvmProg.line(stmt.position)
|
||||
stackvmProg.instruction("continue")
|
||||
TODO("translate CONTINUE")
|
||||
}
|
||||
|
||||
private fun translate(stmt: Break) {
|
||||
stackvmProg.line(stmt.position)
|
||||
stackvmProg.instruction("break")
|
||||
TODO("translate BREAK")
|
||||
}
|
||||
|
||||
private fun translate(branch: BranchStatement) {
|
||||
@ -202,23 +205,23 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
val labelElse = makeLabel("else")
|
||||
val labelContinue = makeLabel("continue")
|
||||
val opcode = when(branch.condition) {
|
||||
BranchCondition.CS -> "bcc"
|
||||
BranchCondition.CC -> "bcs"
|
||||
BranchCondition.EQ -> "bne"
|
||||
BranchCondition.NE -> "beq"
|
||||
BranchCondition.VS -> "bvc"
|
||||
BranchCondition.VC -> "bvs"
|
||||
BranchCondition.MI -> "bpl"
|
||||
BranchCondition.PL -> "bmi"
|
||||
BranchCondition.CS -> Opcode.BCC
|
||||
BranchCondition.CC -> Opcode.BCS
|
||||
BranchCondition.EQ -> Opcode.BNE
|
||||
BranchCondition.NE -> Opcode.BEQ
|
||||
BranchCondition.VS -> TODO("Opcode.BVC")
|
||||
BranchCondition.VC -> TODO("Opcode.BVS")
|
||||
BranchCondition.MI -> Opcode.BPL
|
||||
BranchCondition.PL -> Opcode.BMI
|
||||
}
|
||||
if(branch.elsepart.isEmpty()) {
|
||||
stackvmProg.instruction("$opcode $labelContinue")
|
||||
stackvmProg.instr(opcode, callLabel = labelContinue)
|
||||
translate(branch.statements)
|
||||
stackvmProg.label(labelContinue)
|
||||
} else {
|
||||
stackvmProg.instruction("$opcode $labelElse")
|
||||
stackvmProg.instr(opcode, callLabel = labelElse)
|
||||
translate(branch.statements)
|
||||
stackvmProg.instruction("jump $labelContinue")
|
||||
stackvmProg.instr(Opcode.JUMP, callLabel = labelContinue)
|
||||
stackvmProg.label(labelElse)
|
||||
translate(branch.elsepart)
|
||||
stackvmProg.label(labelContinue)
|
||||
@ -245,13 +248,13 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
val labelElse = makeLabel("else")
|
||||
val labelContinue = makeLabel("continue")
|
||||
if(stmt.elsepart.isEmpty()) {
|
||||
stackvmProg.instruction("beq $labelContinue")
|
||||
stackvmProg.instr(Opcode.BEQ, callLabel = labelContinue)
|
||||
translate(stmt.statements)
|
||||
stackvmProg.label(labelContinue)
|
||||
} else {
|
||||
stackvmProg.instruction("beq $labelElse")
|
||||
stackvmProg.instr(Opcode.BEQ, callLabel = labelElse)
|
||||
translate(stmt.statements)
|
||||
stackvmProg.instruction("jump $labelContinue")
|
||||
stackvmProg.instr(Opcode.JUMP, callLabel = labelContinue)
|
||||
stackvmProg.label(labelElse)
|
||||
translate(stmt.elsepart)
|
||||
stackvmProg.label(labelContinue)
|
||||
@ -261,7 +264,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
private fun translate(expr: IExpression) {
|
||||
when(expr) {
|
||||
is RegisterExpr -> {
|
||||
stackvmProg.instruction("push_var ${expr.register}")
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, expr.register.toString()))
|
||||
}
|
||||
is BinaryExpression -> {
|
||||
translate(expr.left)
|
||||
@ -278,7 +281,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
} else {
|
||||
when(target) {
|
||||
is Subroutine -> {
|
||||
stackvmProg.instruction("call ${target.scopedname}")
|
||||
stackvmProg.instr(Opcode.CALL, callLabel = target.scopedname)
|
||||
}
|
||||
else -> TODO("non-builtin-function call to $target")
|
||||
}
|
||||
@ -288,24 +291,24 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
val target = expr.targetStatement(namespace)
|
||||
when(target) {
|
||||
is VarDecl -> {
|
||||
stackvmProg.instruction("push_var ${target.scopedname}")
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, target.scopedname))
|
||||
}
|
||||
else -> throw CompilerException("expression identifierref should be a vardef, not $target")
|
||||
}
|
||||
}
|
||||
is RangeExpr -> {
|
||||
TODO("TRANSLATE $expr") // todo
|
||||
TODO("TRANSLATE range $expr") // todo
|
||||
}
|
||||
else -> {
|
||||
val lv = expr.constValue(namespace) ?: throw CompilerException("constant expression required, not $expr")
|
||||
when(lv.type) {
|
||||
DataType.BYTE -> stackvmProg.instruction("push b:%02x".format(lv.bytevalue!!))
|
||||
DataType.WORD -> stackvmProg.instruction("push w:%04x".format(lv.wordvalue!!))
|
||||
DataType.FLOAT -> stackvmProg.instruction("push f:${lv.floatvalue}")
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> stackvmProg.instruction("push \"${lv.strvalue}\"")
|
||||
DataType.BYTE -> stackvmProg.instr(Opcode.PUSH, Value(DataType.BYTE, lv.bytevalue))
|
||||
DataType.WORD -> stackvmProg.instr(Opcode.PUSH, Value(DataType.WORD, lv.wordvalue))
|
||||
DataType.FLOAT -> stackvmProg.instr(Opcode.PUSH, Value(DataType.FLOAT, lv.floatvalue))
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> stackvmProg.instr(Opcode.PUSH, Value(DataType.STR,null, lv.strvalue))
|
||||
DataType.ARRAY, DataType.ARRAY_W -> {
|
||||
lv.arrayvalue?.forEach { translate(it) }
|
||||
stackvmProg.instruction("array w:${lv.arrayvalue!!.size.toString(16)}")
|
||||
stackvmProg.instr(Opcode.ARRAY, Value(DataType.WORD, lv.arrayvalue!!.size))
|
||||
}
|
||||
DataType.MATRIX -> TODO("matrix type")
|
||||
}
|
||||
@ -314,28 +317,28 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
}
|
||||
|
||||
private fun translateBinaryOperator(operator: String) {
|
||||
val instruction = when(operator) {
|
||||
"+" -> "add"
|
||||
"-" -> "sub"
|
||||
"*" -> "mul"
|
||||
"/" -> "div"
|
||||
"%" -> "remainder"
|
||||
"**" -> "pow"
|
||||
"&" -> "bitand"
|
||||
"|" -> "bitor"
|
||||
"^" -> "bitxor"
|
||||
"and" -> "and"
|
||||
"or" -> "or"
|
||||
"xor" -> "xor"
|
||||
"<" -> "less"
|
||||
">" -> "greater"
|
||||
"<=" -> "lesseq"
|
||||
">=" -> "greatereq"
|
||||
"==" -> "equal"
|
||||
"!=" -> "notequal"
|
||||
val opcode = when(operator) {
|
||||
"+" -> Opcode.ADD
|
||||
"-" -> Opcode.SUB
|
||||
"*" -> Opcode.MUL
|
||||
"/" -> Opcode.DIV
|
||||
"%" -> Opcode.REMAINDER
|
||||
"**" -> Opcode.POW
|
||||
"&" -> Opcode.BITAND
|
||||
"|" -> Opcode.BITOR
|
||||
"^" -> Opcode.BITXOR
|
||||
"and" -> Opcode.AND
|
||||
"or" -> Opcode.OR
|
||||
"xor" -> Opcode.XOR
|
||||
"<" -> Opcode.LESS
|
||||
">" -> Opcode.GREATER
|
||||
"<=" -> Opcode.LESSEQ
|
||||
">=" -> Opcode.GREATEREQ
|
||||
"==" -> Opcode.EQUAL
|
||||
"!=" -> Opcode.NOTEQUAL
|
||||
else -> throw FatalAstException("const evaluation for invalid operator $operator")
|
||||
}
|
||||
stackvmProg.instruction(instruction)
|
||||
stackvmProg.instr(opcode)
|
||||
}
|
||||
|
||||
private fun translate(stmt: FunctionCallStatement) {
|
||||
@ -354,44 +357,50 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
else -> throw AstException("invalid call target node type: ${targetStmt::class}")
|
||||
}
|
||||
stmt.arglist.forEach { translate(it) }
|
||||
stackvmProg.instruction("call $targetname")
|
||||
stackvmProg.instr(Opcode.CALL, callLabel = targetname)
|
||||
}
|
||||
|
||||
private fun createFunctionCall(funcname: String) {
|
||||
if (funcname.startsWith("_VM_"))
|
||||
stackvmProg.instruction("syscall ${funcname.substring(4)}") // call builtin function
|
||||
else
|
||||
stackvmProg.instruction("syscall FUNC_$funcname")
|
||||
val function = (
|
||||
if (funcname.startsWith("_VM_"))
|
||||
funcname.substring(4)
|
||||
else
|
||||
"FUNC_$funcname"
|
||||
).toUpperCase()
|
||||
val callNr = Syscall.valueOf(function).callNr
|
||||
stackvmProg.instr(Opcode.SYSCALL, Value(DataType.BYTE, callNr))
|
||||
}
|
||||
|
||||
private fun translate(stmt: Jump) {
|
||||
val instr =
|
||||
if(stmt.address!=null) {
|
||||
"jump \$${stmt.address.toString(16)}"
|
||||
} else {
|
||||
val target = stmt.identifier!!.targetStatement(namespace)!!
|
||||
when(target) {
|
||||
is Label -> "jump ${target.scopedname}"
|
||||
is Subroutine -> "jump ${target.scopedname}"
|
||||
else -> throw CompilerException("invalid jump target type ${target::class}")
|
||||
}
|
||||
}
|
||||
var jumpAddress: Value? = null
|
||||
var jumpLabel: String? = null
|
||||
|
||||
if(stmt.address!=null) {
|
||||
jumpAddress = Value(DataType.WORD, stmt.address)
|
||||
} else {
|
||||
val target = stmt.identifier!!.targetStatement(namespace)!!
|
||||
jumpLabel = when(target) {
|
||||
is Label -> target.scopedname
|
||||
is Subroutine -> target.scopedname
|
||||
else -> throw CompilerException("invalid jump target type ${target::class}")
|
||||
}
|
||||
}
|
||||
stackvmProg.line(stmt.position)
|
||||
stackvmProg.instruction(instr)
|
||||
stackvmProg.instr(Opcode.JUMP, jumpAddress, jumpLabel)
|
||||
}
|
||||
|
||||
private fun translate(stmt: PostIncrDecr) {
|
||||
stackvmProg.line(stmt.position)
|
||||
if(stmt.target.register!=null) {
|
||||
when(stmt.operator) {
|
||||
"++" -> stackvmProg.instruction("inc_var ${stmt.target.register}")
|
||||
"--" -> stackvmProg.instruction("dec_var ${stmt.target.register}")
|
||||
"++" -> stackvmProg.instr(Opcode.INC_VAR, Value(DataType.STR, null, stmt.target.register.toString()))
|
||||
"--" -> stackvmProg.instr(Opcode.DEC_VAR, Value(DataType.STR, null, stmt.target.register.toString()))
|
||||
}
|
||||
} else {
|
||||
val targetStatement = stmt.target.identifier!!.targetStatement(namespace) as VarDecl
|
||||
when(stmt.operator) {
|
||||
"++" -> stackvmProg.instruction("inc_var ${targetStatement.scopedname}")
|
||||
"--" -> stackvmProg.instruction("dec_var ${targetStatement.scopedname}")
|
||||
"++" -> stackvmProg.instr(Opcode.INC_VAR, Value(DataType.STR, null, targetStatement.scopedname))
|
||||
"--" -> stackvmProg.instr(Opcode.DEC_VAR, Value(DataType.STR, null, targetStatement.scopedname))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -407,14 +416,14 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
DataType.BYTE -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
|
||||
DataType.WORD -> {
|
||||
if(valueDt==DataType.BYTE)
|
||||
stackvmProg.instruction("b2word")
|
||||
stackvmProg.instr(Opcode.B2WORD)
|
||||
else
|
||||
throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
when (valueDt) {
|
||||
DataType.BYTE -> stackvmProg.instruction("b2float")
|
||||
DataType.WORD -> stackvmProg.instruction("w2float")
|
||||
DataType.BYTE -> stackvmProg.instr(Opcode.B2FLOAT)
|
||||
DataType.WORD -> stackvmProg.instr(Opcode.W2FLOAT)
|
||||
else -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
|
||||
}
|
||||
}
|
||||
@ -429,11 +438,11 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
if(stmt.target.identifier!=null) {
|
||||
val target = stmt.target.identifier!!.targetStatement(namespace)!!
|
||||
when(target) {
|
||||
is VarDecl -> stackvmProg.instruction("push_var ${target.scopedname}")
|
||||
is VarDecl -> stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, target.scopedname))
|
||||
else -> throw CompilerException("invalid assignment target type ${target::class}")
|
||||
}
|
||||
} else if(stmt.target.register!=null) {
|
||||
stackvmProg.instruction("push_var ${stmt.target.register}")
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, stmt.target.register.toString()))
|
||||
}
|
||||
translateAugAssignOperator(stmt.aug_op)
|
||||
}
|
||||
@ -442,27 +451,27 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
if(stmt.target.identifier!=null) {
|
||||
val target = stmt.target.identifier!!.targetStatement(namespace)!!
|
||||
when(target) {
|
||||
is VarDecl -> stackvmProg.instruction("pop_var ${target.scopedname}")
|
||||
is VarDecl -> stackvmProg.instr(Opcode.POP_VAR, Value(DataType.STR, null, target.scopedname))
|
||||
else -> throw CompilerException("invalid assignment target type ${target::class}")
|
||||
}
|
||||
} else if(stmt.target.register!=null) {
|
||||
stackvmProg.instruction("pop_var ${stmt.target.register}")
|
||||
stackvmProg.instr(Opcode.POP_VAR, Value(DataType.STR, null, stmt.target.register.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateAugAssignOperator(aug_op: String) {
|
||||
val instruction = when(aug_op) {
|
||||
"+=" -> "add"
|
||||
"-=" -> "sub"
|
||||
"/=" -> "div"
|
||||
"*=" -> "mul"
|
||||
"**=" -> "pow"
|
||||
"&=" -> "bitand"
|
||||
"|=" -> "bitor"
|
||||
"^=" -> "bitxor"
|
||||
val opcode = when(aug_op) {
|
||||
"+=" -> Opcode.ADD
|
||||
"-=" -> Opcode.SUB
|
||||
"/=" -> Opcode.DIV
|
||||
"*=" -> Opcode.MUL
|
||||
"**=" -> Opcode.POW
|
||||
"&=" -> Opcode.BITAND
|
||||
"|=" -> Opcode.BITOR
|
||||
"^=" -> Opcode.BITXOR
|
||||
else -> throw CompilerException("invalid aug assignment operator $aug_op")
|
||||
}
|
||||
stackvmProg.instruction(instruction)
|
||||
stackvmProg.instr(opcode)
|
||||
}
|
||||
|
||||
private fun translate(stmt: Return) {
|
||||
@ -470,161 +479,32 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva
|
||||
TODO("return with value(s) not yet supported: $stmt")
|
||||
}
|
||||
stackvmProg.line(stmt.position)
|
||||
stackvmProg.instruction("return")
|
||||
stackvmProg.instr(Opcode.RETURN)
|
||||
}
|
||||
|
||||
private fun translate(stmt: Label) {
|
||||
stackvmProg.label(stmt.scopedname)
|
||||
}
|
||||
|
||||
|
||||
private fun translate(loop: ForLoop) {
|
||||
stackvmProg.line(loop.position)
|
||||
if(loop.loopRegister!=null) {
|
||||
val reg = loop.loopRegister
|
||||
println("@TODO translate for loop (register $reg)") // TODO
|
||||
if(loop.iterable is RangeExpr) {
|
||||
val range = (loop.iterable as RangeExpr).toKotlinRange()
|
||||
if(range.isEmpty())
|
||||
throw CompilerException("loop over empty range")
|
||||
if(range.step==1) {
|
||||
println("@todo for loop, register, range, step 1") // todo
|
||||
} else {
|
||||
TODO("loop over range with step != 1")
|
||||
}
|
||||
} else {
|
||||
TODO("loop over something else as a Range: ${loop.iterable}")
|
||||
}
|
||||
} else {
|
||||
val loopvar = (loop.loopVar!!.targetStatement(namespace) as VarDecl)
|
||||
println("@TODO translate for loop (variable $loopvar)") // TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class StackVmProgram(val name: String) {
|
||||
|
||||
val variables = mutableMapOf<String, VarDecl>()
|
||||
val instructions = mutableListOf<String>()
|
||||
val variablesMemSize: Int
|
||||
get() {
|
||||
return variables.values.fold(0) { acc, vardecl -> acc+vardecl.memorySize}
|
||||
}
|
||||
|
||||
fun optimize() {
|
||||
println("\nOptimizing stackVM code...")
|
||||
// todo optimize stackvm code
|
||||
}
|
||||
|
||||
fun compileToAssembly(): AssemblyResult {
|
||||
println("\nGenerating assembly code from stackvmProg code... ")
|
||||
// todo generate 6502 assembly
|
||||
return AssemblyResult(name)
|
||||
}
|
||||
|
||||
fun blockvar(scopedname: String, decl: VarDecl) {
|
||||
variables[scopedname] = decl
|
||||
}
|
||||
|
||||
fun toTextLines() : List<String> {
|
||||
val result = mutableListOf("; stackvm program code for: $name")
|
||||
result.add("%memory")
|
||||
// todo memory lines for initial memory initialization
|
||||
result.add("%end_memory")
|
||||
result.add("%variables")
|
||||
for(v in variables) {
|
||||
if (!(v.value.type == VarDeclType.VAR || v.value.type == VarDeclType.CONST)) {
|
||||
throw AssertionError("Should be only VAR or CONST variables")
|
||||
}
|
||||
val litval = v.value.value as LiteralValue
|
||||
val litvalStr = when(litval.type) {
|
||||
DataType.BYTE -> litval.bytevalue!!.toString(16)
|
||||
DataType.WORD -> litval.wordvalue!!.toString(16)
|
||||
DataType.FLOAT -> litval.floatvalue.toString()
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> "\"${litval.strvalue}\""
|
||||
else -> TODO("non-scalar value")
|
||||
}
|
||||
val line = "${v.key} ${v.value.datatype.toString().toLowerCase()} $litvalStr"
|
||||
result.add(line)
|
||||
}
|
||||
result.add("%end_variables")
|
||||
result.add("%instructions")
|
||||
result.addAll(instructions)
|
||||
result.add("%end_instructions")
|
||||
return result
|
||||
}
|
||||
|
||||
fun instruction(s: String) {
|
||||
instructions.add(" $s")
|
||||
}
|
||||
|
||||
fun label(name: String) {
|
||||
instructions.add("$name:")
|
||||
}
|
||||
|
||||
fun line(position: Position) {
|
||||
instructions.add(" _line ${position.line} ${position.file}")
|
||||
}
|
||||
}
|
||||
|
||||
enum class OutputType {
|
||||
RAW,
|
||||
PRG
|
||||
}
|
||||
|
||||
enum class LauncherType {
|
||||
BASIC,
|
||||
NONE
|
||||
}
|
||||
|
||||
enum class ZeropageType {
|
||||
BASICSAFE,
|
||||
KERNALSAFE,
|
||||
FULL
|
||||
}
|
||||
|
||||
|
||||
data class CompilationOptions(val output: OutputType,
|
||||
val launcher: LauncherType,
|
||||
val zeropage: ZeropageType,
|
||||
val floats: Boolean)
|
||||
|
||||
|
||||
class AssemblyResult(val name: String) {
|
||||
fun assemble(options: CompilationOptions, inputfilename: String, outputfilename: String) {
|
||||
println("\nGenerating machine code program...")
|
||||
|
||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "-Wall", "-Wno-strict-bool",
|
||||
"--dump-labels", "--vice-labels", "-l", "$outputfilename.vice-mon-list",
|
||||
"--no-monitor", "--output", outputfilename, inputfilename)
|
||||
|
||||
when(options.output) {
|
||||
OutputType.PRG -> {
|
||||
command.add("--cbm-prg")
|
||||
println("\nCreating C-64 prg.")
|
||||
}
|
||||
OutputType.RAW -> {
|
||||
command.add("--nostart")
|
||||
println("\nCreating raw binary.")
|
||||
}
|
||||
}
|
||||
|
||||
val proc = ProcessBuilder(command).inheritIO().start()
|
||||
val result = proc.waitFor()
|
||||
if(result!=0) {
|
||||
System.err.println("assembler failed with returncode $result")
|
||||
exitProcess(result)
|
||||
}
|
||||
}
|
||||
|
||||
fun generateBreakpointList(): String {
|
||||
// todo build breakpoint list
|
||||
/*
|
||||
def generate_breakpoint_list(self, program_filename: str) -> str:
|
||||
breakpoints = []
|
||||
vice_mon_file = program_filename + ".vice-mon-list"
|
||||
with open(vice_mon_file, "rU") as f:
|
||||
for line in f:
|
||||
match = re.fullmatch(r"al (?P<address>\w+) \S+_prog8_breakpoint_\d+.?", line, re.DOTALL)
|
||||
if match:
|
||||
breakpoints.append("$" + match.group("address"))
|
||||
with open(vice_mon_file, "at") as f:
|
||||
print("; vice monitor breakpoint list now follows", file=f)
|
||||
print("; {:d} breakpoints have been defined here".format(len(breakpoints)), file=f)
|
||||
print("del", file=f)
|
||||
for b in breakpoints:
|
||||
print("break", b, file=f)
|
||||
return vice_mon_file
|
||||
*/
|
||||
return "monitorfile.txt"
|
||||
}
|
||||
}
|
@ -3,50 +3,16 @@ package prog8.compiler
|
||||
import prog8.ast.*
|
||||
|
||||
|
||||
class Zeropage(private val options: CompilationOptions) {
|
||||
|
||||
companion object {
|
||||
const val SCRATCH_B1 = 0x02
|
||||
const val SCRATCH_B2 = 0x03
|
||||
const val SCRATCH_W1 = 0xfb // $fb/$fc
|
||||
const val SCRATCH_W2 = 0xfd // $fd/$fe
|
||||
}
|
||||
abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
|
||||
private val allocations = mutableMapOf<Int, Pair<String, DataType>>()
|
||||
val free = mutableListOf<Int>()
|
||||
|
||||
init {
|
||||
if(options.zeropage==ZeropageType.FULL) {
|
||||
free.addAll(0x04 .. 0xfa)
|
||||
free.add(0xff)
|
||||
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
|
||||
} else {
|
||||
if(options.zeropage==ZeropageType.KERNALSAFE) {
|
||||
// add the Zp addresses that are just used by BASIC routines to the free list
|
||||
free.addAll(listOf(0x09, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x53, 0x6f, 0x70))
|
||||
}
|
||||
// add the Zp addresses not even used by BASIC
|
||||
// these are valid for the C-64 (when no RS232 I/O is performed):
|
||||
// ($02, $03, $fb-$fc, $fd-$fe are reserved as scratch addresses for various routines)
|
||||
// KNOWN WORKING FREE: 0x04, 0x05, 0x06, 0x2a, 0x52, 0xf7, 0xf8, 0xf9, 0xfa))
|
||||
free.addAll(listOf(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0d, 0x0e,
|
||||
0x12, 0x2a, 0x52, 0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
|
||||
0xb5, 0xb6, 0xf7, 0xf8, 0xf9, 0xfa))
|
||||
}
|
||||
assert(!free.contains(Zeropage.SCRATCH_B1))
|
||||
assert(!free.contains(Zeropage.SCRATCH_B2))
|
||||
assert(!free.contains(Zeropage.SCRATCH_W1))
|
||||
assert(!free.contains(Zeropage.SCRATCH_W2))
|
||||
}
|
||||
val free = mutableListOf<Int>() // subclasses must set this to the appropriate free locations.
|
||||
|
||||
fun available() = free.size
|
||||
|
||||
fun allocate(vardecl: VarDecl) : Int {
|
||||
assert(vardecl.name.isEmpty() || !allocations.values.any { it.first==vardecl.name } ) {"same name can't be allocated twice"}
|
||||
assert(vardecl.type==VarDeclType.VAR) {"can only allocate VAR type"}
|
||||
assert(vardecl.type== VarDeclType.VAR) {"can only allocate VAR type"}
|
||||
|
||||
val size =
|
||||
if(vardecl.arrayspec!=null) {
|
||||
|
167
compiler/src/prog8/compiler/target/c64/Commodore64.kt
Normal file
167
compiler/src/prog8/compiler/target/c64/Commodore64.kt
Normal file
@ -0,0 +1,167 @@
|
||||
package prog8.compiler.target.c64
|
||||
|
||||
import prog8.compiler.*
|
||||
import prog8.stackvm.Program
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.pow
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
// 5-byte cbm MFLPT format limitations:
|
||||
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38
|
||||
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38
|
||||
|
||||
|
||||
|
||||
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
companion object {
|
||||
const val SCRATCH_B1 = 0x02
|
||||
const val SCRATCH_B2 = 0x03
|
||||
const val SCRATCH_W1 = 0xfb // $fb/$fc
|
||||
const val SCRATCH_W2 = 0xfd // $fd/$fe
|
||||
}
|
||||
|
||||
init {
|
||||
if(options.zeropage== ZeropageType.FULL) {
|
||||
free.addAll(0x04 .. 0xfa)
|
||||
free.add(0xff)
|
||||
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
|
||||
} else {
|
||||
if(options.zeropage== ZeropageType.KERNALSAFE) {
|
||||
// add the Zp addresses that are just used by BASIC routines to the free list
|
||||
free.addAll(listOf(0x09, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x53, 0x6f, 0x70))
|
||||
}
|
||||
// add the Zp addresses not even used by BASIC
|
||||
// these are valid for the C-64 (when no RS232 I/O is performed):
|
||||
// ($02, $03, $fb-$fc, $fd-$fe are reserved as scratch addresses for various routines)
|
||||
// KNOWN WORKING FREE: 0x04, 0x05, 0x06, 0x2a, 0x52, 0xf7, 0xf8, 0xf9, 0xfa))
|
||||
free.addAll(listOf(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0d, 0x0e,
|
||||
0x12, 0x2a, 0x52, 0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
|
||||
0xb5, 0xb6, 0xf7, 0xf8, 0xf9, 0xfa))
|
||||
}
|
||||
assert(!free.contains(SCRATCH_B1))
|
||||
assert(!free.contains(SCRATCH_B2))
|
||||
assert(!free.contains(SCRATCH_W1))
|
||||
assert(!free.contains(SCRATCH_W2))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) {
|
||||
|
||||
companion object {
|
||||
val zero = Mflpt5(0, 0,0,0,0)
|
||||
fun fromNumber(num: Number): Mflpt5 {
|
||||
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
|
||||
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
|
||||
// and https://en.wikipedia.org/wiki/IEEE_754-1985
|
||||
|
||||
val flt = num.toDouble()
|
||||
if(flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
|
||||
throw CompilerException("floating point number out of 5-byte mflpt range: $this")
|
||||
if(flt==0.0)
|
||||
return zero
|
||||
|
||||
val sign = if(flt<0.0) 0x80L else 0x00L
|
||||
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
|
||||
var mantissa = flt.absoluteValue
|
||||
|
||||
// if mantissa is too large, shift right and adjust exponent
|
||||
while(mantissa >= 0x100000000) {
|
||||
mantissa /= 2.0
|
||||
exponent ++
|
||||
}
|
||||
// if mantissa is too small, shift left and adjust exponent
|
||||
while(mantissa < 0x80000000) {
|
||||
mantissa *= 2.0
|
||||
exponent --
|
||||
}
|
||||
|
||||
return when {
|
||||
exponent<0 -> zero // underflow, use zero instead
|
||||
exponent>255 -> throw CompilerException("floating point overflow: $this")
|
||||
exponent==0 -> zero
|
||||
else -> {
|
||||
val mantLong = mantissa.toLong()
|
||||
Mflpt5(
|
||||
exponent.toShort(),
|
||||
(mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(),
|
||||
(mantLong.and(0x00ff0000L) ushr 16).toShort(),
|
||||
(mantLong.and(0x0000ff00L) ushr 8).toShort(),
|
||||
(mantLong.and(0x000000ffL)).toShort())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toDouble(): Double {
|
||||
if(this == zero) return 0.0
|
||||
val exp = b0 - 128
|
||||
val sign = (b1.toInt() and 0x80) > 0
|
||||
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
|
||||
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
|
||||
return if(sign) -result else result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun compileToAssembly(program: Program): AssemblyResult {
|
||||
println("\nGenerating assembly code from stackvmProg code... ")
|
||||
// todo generate 6502 assembly
|
||||
return AssemblyResult(program.name)
|
||||
}
|
||||
|
||||
|
||||
class AssemblyResult(val name: String) {
|
||||
fun assemble(options: CompilationOptions, inputfilename: String, outputfilename: String) {
|
||||
println("\nGenerating machine code program...")
|
||||
|
||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "-Wall", "-Wno-strict-bool",
|
||||
"--dump-labels", "--vice-labels", "-l", "$outputfilename.vice-mon-list",
|
||||
"--no-monitor", "--output", outputfilename, inputfilename)
|
||||
|
||||
when(options.output) {
|
||||
OutputType.PRG -> {
|
||||
command.add("--cbm-prg")
|
||||
println("\nCreating C-64 prg.")
|
||||
}
|
||||
OutputType.RAW -> {
|
||||
command.add("--nostart")
|
||||
println("\nCreating raw binary.")
|
||||
}
|
||||
}
|
||||
|
||||
val proc = ProcessBuilder(command).inheritIO().start()
|
||||
val result = proc.waitFor()
|
||||
if(result!=0) {
|
||||
System.err.println("assembler failed with returncode $result")
|
||||
exitProcess(result)
|
||||
}
|
||||
}
|
||||
|
||||
fun generateBreakpointList(): String {
|
||||
// todo build breakpoint list
|
||||
/*
|
||||
def generate_breakpoint_list(self, program_filename: str) -> str:
|
||||
breakpoints = []
|
||||
vice_mon_file = program_filename + ".vice-mon-list"
|
||||
with open(vice_mon_file, "rU") as f:
|
||||
for line in f:
|
||||
match = re.fullmatch(r"al (?P<address>\w+) \S+_prog8_breakpoint_\d+.?", line, re.DOTALL)
|
||||
if match:
|
||||
breakpoints.append("$" + match.group("address"))
|
||||
with open(vice_mon_file, "at") as f:
|
||||
print("; vice monitor breakpoint list now follows", file=f)
|
||||
print("; {:d} breakpoints have been defined here".format(len(breakpoints)), file=f)
|
||||
print("del", file=f)
|
||||
for b in breakpoints:
|
||||
print("break", b, file=f)
|
||||
return vice_mon_file
|
||||
*/
|
||||
return "monitorfile.txt"
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
package prog8.compiler
|
||||
package prog8.compiler.target.c64
|
||||
|
||||
import prog8.compiler.CompilerException
|
||||
|
||||
class Petscii {
|
||||
companion object {
|
@ -1,7 +1,7 @@
|
||||
package prog8.optimizing
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.Petscii
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
|
||||
|
||||
class ConstantFolding(private val namespace: INameScope) : IAstProcessor {
|
||||
|
@ -1,10 +1,10 @@
|
||||
package prog8.parser
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams
|
||||
import prog8.ast.*
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import prog8.ast.*
|
||||
|
||||
|
||||
class ParsingFailedError(override var message: String) : Exception(message)
|
||||
|
@ -1,8 +1,10 @@
|
||||
package prog8.stackvm
|
||||
|
||||
import javax.swing.*
|
||||
import java.awt.*
|
||||
import java.awt.image.BufferedImage
|
||||
import javax.swing.JButton
|
||||
import javax.swing.JFrame
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.Timer
|
||||
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
package prog8.stackvm
|
||||
|
||||
import prog8.ast.DataType
|
||||
import prog8.compiler.Mflpt5
|
||||
import prog8.compiler.Petscii
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.compiler.target.c64.Mflpt5
|
||||
import java.awt.EventQueue
|
||||
import java.io.File
|
||||
import java.io.PrintStream
|
||||
import java.io.PrintWriter
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
import javax.swing.Timer
|
||||
@ -118,7 +117,7 @@ enum class Opcode {
|
||||
SEC, // set carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations
|
||||
CLC, // clear carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations
|
||||
NOP,
|
||||
BREAK, // breakpoint
|
||||
BREAKPOINT, // breakpoint
|
||||
TERMINATE, // end the program
|
||||
LINE // record source file line number
|
||||
}
|
||||
@ -228,7 +227,7 @@ class Memory {
|
||||
}
|
||||
|
||||
|
||||
class Value(val type: DataType, private val numericvalue: Number?, val stringvalue: String?=null, val arrayvalue: IntArray?=null) {
|
||||
class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?=null, val arrayvalue: IntArray?=null) {
|
||||
private var byteval: Short? = null
|
||||
private var wordval: Int? = null
|
||||
private var floatval: Double? = null
|
||||
@ -251,7 +250,7 @@ class Value(val type: DataType, private val numericvalue: Number?, val stringval
|
||||
DataType.ARRAY -> {
|
||||
asBooleanValue = arrayvalue!!.isNotEmpty()
|
||||
}
|
||||
DataType.STR -> {
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
|
||||
if(stringvalue==null) throw VmExecutionException("expect stringvalue for STR type")
|
||||
asBooleanValue = stringvalue.isNotEmpty()
|
||||
}
|
||||
@ -261,9 +260,9 @@ class Value(val type: DataType, private val numericvalue: Number?, val stringval
|
||||
|
||||
override fun toString(): String {
|
||||
return when(type) {
|
||||
DataType.BYTE -> "%02x".format(byteval)
|
||||
DataType.WORD -> "%04x".format(wordval)
|
||||
DataType.FLOAT -> floatval.toString()
|
||||
DataType.BYTE -> "b:%02x".format(byteval)
|
||||
DataType.WORD -> "w:%04x".format(wordval)
|
||||
DataType.FLOAT -> "f:$floatval"
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> "\"$stringvalue\""
|
||||
DataType.ARRAY -> TODO("array")
|
||||
DataType.ARRAY_W -> TODO("word array")
|
||||
@ -513,6 +512,7 @@ open class Instruction(val opcode: Opcode,
|
||||
val argStr = arg?.toString() ?: ""
|
||||
val result =
|
||||
when {
|
||||
opcode==Opcode.LINE -> "_line ${arg!!.stringvalue}"
|
||||
opcode==Opcode.SYSCALL -> {
|
||||
val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() }
|
||||
"syscall $syscall"
|
||||
@ -522,13 +522,11 @@ open class Instruction(val opcode: Opcode,
|
||||
}
|
||||
.trimEnd()
|
||||
|
||||
if(opcode==Opcode.LINE)
|
||||
return " _$result"
|
||||
return " $result"
|
||||
}
|
||||
}
|
||||
|
||||
class Label(val name: String) : Instruction(opcode = Opcode.NOP) {
|
||||
class LabelInstr(val name: String) : Instruction(opcode = Opcode.NOP) {
|
||||
override fun toString(): String {
|
||||
return "$name:"
|
||||
}
|
||||
@ -547,7 +545,7 @@ private class MyStack<T> : Stack<T>() {
|
||||
|
||||
fun pop2() : Pair<T, T> = Pair(pop(), pop())
|
||||
|
||||
fun printTop(amount: Int, output: PrintWriter) {
|
||||
fun printTop(amount: Int, output: PrintStream) {
|
||||
peek(amount).reversed().forEach { output.println(" $it") }
|
||||
}
|
||||
}
|
||||
@ -659,10 +657,12 @@ class Program (val name: String,
|
||||
if(line=="%end_variables")
|
||||
return vars
|
||||
val (name, type, valueStr) = line.split(splitpattern, limit = 3)
|
||||
if(valueStr[0] !='"' && valueStr[1]!=':')
|
||||
throw VmExecutionException("missing value type character")
|
||||
val value = when(type) {
|
||||
"byte" -> Value(DataType.BYTE, valueStr.toShort(16))
|
||||
"word" -> Value(DataType.WORD, valueStr.toInt(16))
|
||||
"float" -> Value(DataType.FLOAT, valueStr.toDouble())
|
||||
"byte" -> Value(DataType.BYTE, valueStr.substring(2).toShort(16))
|
||||
"word" -> Value(DataType.WORD, valueStr.substring(2).toInt(16))
|
||||
"float" -> Value(DataType.FLOAT, valueStr.substring(2).toDouble())
|
||||
"str", "str_p", "str_s", "str_ps" -> {
|
||||
if(valueStr.startsWith('"') && valueStr.endsWith('"'))
|
||||
Value(DataType.STR, null, unescape(valueStr.substring(1, valueStr.length-1)))
|
||||
@ -780,7 +780,7 @@ class Program (val name: String,
|
||||
}
|
||||
}
|
||||
|
||||
fun print(out: PrintStream) {
|
||||
fun print(out: PrintStream, embeddedLabels: Boolean=true) {
|
||||
out.println("; stackVM program code for '$name'")
|
||||
out.println("%memory")
|
||||
if(memory.isNotEmpty()) {
|
||||
@ -796,10 +796,13 @@ class Program (val name: String,
|
||||
out.println("%instructions")
|
||||
val labels = this.labels.entries.associateBy({it.value}) {it.key}
|
||||
for(instr in this.program) {
|
||||
val label = labels[instr]
|
||||
if(label!=null)
|
||||
out.println("$label:")
|
||||
out.println(instr)
|
||||
if(!embeddedLabels) {
|
||||
val label = labels[instr]
|
||||
if (label != null)
|
||||
out.println("$label:")
|
||||
} else {
|
||||
out.println(instr)
|
||||
}
|
||||
}
|
||||
out.println("%end_instructions")
|
||||
}
|
||||
@ -813,7 +816,7 @@ class StackVm(val traceOutputFile: String?) {
|
||||
private var variables = mutableMapOf<String, Value>() // all variables (set of all vars used by all blocks/subroutines) key = their fully scoped name
|
||||
private var carry: Boolean = false
|
||||
private var program = listOf<Instruction>()
|
||||
private var traceOutput = if(traceOutputFile!=null) File(traceOutputFile).printWriter() else null
|
||||
private var traceOutput = if(traceOutputFile!=null) PrintStream(File(traceOutputFile), "utf-8") else null
|
||||
private lateinit var currentIns: Instruction
|
||||
private lateinit var canvas: BitmapScreenPanel
|
||||
private val rnd = Random()
|
||||
@ -1105,7 +1108,7 @@ class StackVm(val traceOutputFile: String?) {
|
||||
Opcode.SEC -> carry = true
|
||||
Opcode.CLC -> carry = false
|
||||
Opcode.TERMINATE -> throw VmTerminationException("terminate instruction")
|
||||
Opcode.BREAK -> throw VmBreakpointException()
|
||||
Opcode.BREAKPOINT -> throw VmBreakpointException()
|
||||
|
||||
Opcode.INC_MEM -> {
|
||||
val addr = ins.arg!!.integerValue()
|
||||
@ -1374,8 +1377,6 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
|
||||
val program = Program.load(args.first())
|
||||
program.print(System.out)
|
||||
|
||||
val vm = StackVm(traceOutputFile = null)
|
||||
val dialog = ScreenDialog()
|
||||
vm.load(program, dialog.canvas)
|
||||
|
@ -5,6 +5,7 @@ import prog8.ast.Position
|
||||
import prog8.ast.VarDecl
|
||||
import prog8.ast.VarDeclType
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.c64.*
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.closeTo
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
@ -114,7 +115,7 @@ private val dummypos = Position("test", 0, 0, 0)
|
||||
class TestZeropage {
|
||||
@Test
|
||||
fun testNames() {
|
||||
val zp = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, false))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, false))
|
||||
|
||||
assertFailsWith<AssertionError> {
|
||||
zp.allocate(VarDecl(VarDeclType.MEMORY, DataType.BYTE, null, "", null, dummypos))
|
||||
@ -131,27 +132,27 @@ class TestZeropage {
|
||||
|
||||
@Test
|
||||
fun testZpFloatEnable() {
|
||||
val zp = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, false))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, false))
|
||||
assertFailsWith<CompilerException> {
|
||||
zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, null, "", null, dummypos))
|
||||
}
|
||||
val zp2 = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, true))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, true))
|
||||
zp2.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, null, "", null, dummypos))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFreeSpaces() {
|
||||
val zp1 = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, true))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, true))
|
||||
assertEquals(23, zp1.available())
|
||||
val zp2 = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, true))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, true))
|
||||
assertEquals(71, zp2.available())
|
||||
val zp3 = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, true))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, true))
|
||||
assertEquals(239, zp3.available())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBasicsafeAllocation() {
|
||||
val zp = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, true))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, true))
|
||||
assertEquals(23, zp.available())
|
||||
|
||||
zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, null, "", null, dummypos))
|
||||
@ -175,7 +176,7 @@ class TestZeropage {
|
||||
|
||||
@Test
|
||||
fun testFullAllocation() {
|
||||
val zp = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, true))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, true))
|
||||
assertEquals(239, zp.available())
|
||||
val loc = zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, null, "", null, dummypos))
|
||||
assertTrue(loc > 3)
|
||||
@ -212,7 +213,7 @@ class TestZeropage {
|
||||
// free = (0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0d, 0x0e,
|
||||
// 0x12, 0x2a, 0x52, 0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
|
||||
// 0xb5, 0xb6, 0xf7, 0xf8, 0xf9, 0xfa))
|
||||
val zp = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, true))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, true))
|
||||
assertEquals(23, zp.available())
|
||||
assertEquals(0x04, zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, null, "", null, dummypos)))
|
||||
assertEquals(0x09, zp.allocate(VarDecl(VarDeclType.VAR, DataType.BYTE, null, "", null, dummypos)))
|
||||
|
Loading…
x
Reference in New Issue
Block a user