mirror of
https://github.com/irmen/prog8.git
synced 2025-01-11 13:29:45 +00:00
screen added to vm, some new opcodes
This commit is contained in:
parent
af7103f0f7
commit
e71fef709a
@ -11,19 +11,25 @@ main.var1 str "This is main.var1"
|
||||
main.var2 byte aa
|
||||
main.var3 word ea44
|
||||
main.var4 float 3.1415927
|
||||
main.textcolor byte 0
|
||||
input.prompt str "Enter a number: "
|
||||
input.result word 0
|
||||
%end_variables
|
||||
; instructions and labels
|
||||
%instructions
|
||||
nop
|
||||
syscall WRITE_MEMSTR word:1000
|
||||
syscall WRITE_MEMSTR w:1000
|
||||
loop:
|
||||
syscall WRITE_VAR str:"input.prompt"
|
||||
syscall INPUT_VAR str:"input.result"
|
||||
syscall WRITE_VAR str:"input.result"
|
||||
push byte:8d
|
||||
syscall WRITE_CHAR
|
||||
inc_var "main.textcolor"
|
||||
syscall RANDOM
|
||||
syscall RANDOM
|
||||
push_var "main.textcolor"
|
||||
syscall GFX_PIXEL
|
||||
; syscall WRITE_VAR "input.prompt"
|
||||
; syscall INPUT_VAR "input.result"
|
||||
; syscall WRITE_VAR "input.result"
|
||||
; push b:8d
|
||||
; syscall WRITE_CHAR
|
||||
jump loop
|
||||
%end_instructions
|
||||
|
||||
|
@ -544,14 +544,6 @@ class VarDecl(val type: VarDeclType,
|
||||
|
||||
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
|
||||
|
||||
|
||||
fun arraySizeX(namespace: INameScope) : Int? {
|
||||
return arrayspec?.x?.constValue(namespace)?.intvalue
|
||||
}
|
||||
fun arraySizeY(namespace: INameScope) : Int? {
|
||||
return arrayspec?.y?.constValue(namespace)?.intvalue
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, value=$value, pos=$position)"
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ fun Module.checkValid(globalNamespace: INameScope, compilerOptions: CompilationO
|
||||
* todo check subroutine return values against the call's result assignments
|
||||
*/
|
||||
|
||||
class AstChecker(private val globalNamespace: INameScope, private val compilerOptions: CompilationOptions) : IAstProcessor {
|
||||
class AstChecker(private val namespace: INameScope, private val compilerOptions: CompilationOptions) : IAstProcessor {
|
||||
private val checkResult: MutableList<SyntaxError> = mutableListOf()
|
||||
|
||||
fun result(): List<SyntaxError> {
|
||||
@ -122,7 +122,7 @@ class AstChecker(private val globalNamespace: INameScope, private val compilerOp
|
||||
*/
|
||||
override fun process(assignment: Assignment): IStatement {
|
||||
if(assignment.target.identifier!=null) {
|
||||
val targetSymbol = globalNamespace.lookup(assignment.target.identifier!!.nameInSource, assignment)
|
||||
val targetSymbol = namespace.lookup(assignment.target.identifier!!.nameInSource, assignment)
|
||||
if(targetSymbol !is VarDecl) {
|
||||
checkResult.add(SyntaxError("assignment LHS must be register or variable", assignment.position))
|
||||
return super.process(assignment)
|
||||
@ -133,7 +133,7 @@ class AstChecker(private val globalNamespace: INameScope, private val compilerOp
|
||||
}
|
||||
|
||||
if(assignment.value is LiteralValue) {
|
||||
val targetDatatype = assignment.target.determineDatatype(globalNamespace, assignment)
|
||||
val targetDatatype = assignment.target.determineDatatype(namespace, assignment)
|
||||
checkValueTypeAndRange(targetDatatype, null, assignment.value as LiteralValue, assignment.position)
|
||||
}
|
||||
return super.process(assignment)
|
||||
@ -191,7 +191,7 @@ class AstChecker(private val globalNamespace: INameScope, private val compilerOp
|
||||
* check if condition
|
||||
*/
|
||||
override fun process(ifStatement: IfStatement): IStatement {
|
||||
val constvalue = ifStatement.condition.constValue(globalNamespace)
|
||||
val constvalue = ifStatement.condition.constValue(namespace)
|
||||
if(constvalue!=null) {
|
||||
val msg = if (constvalue.asBooleanValue) "condition is always true" else "condition is always false"
|
||||
println("${ifStatement.position} Warning: $msg")
|
||||
@ -279,8 +279,8 @@ class AstChecker(private val globalNamespace: INameScope, private val compilerOp
|
||||
checkResult.add(SyntaxError(msg, range.position))
|
||||
}
|
||||
super.process(range)
|
||||
val from = range.from.constValue(globalNamespace)
|
||||
val to = range.to.constValue(globalNamespace)
|
||||
val from = range.from.constValue(namespace)
|
||||
val to = range.to.constValue(namespace)
|
||||
if(from!=null && to != null) {
|
||||
when {
|
||||
from.intvalue!=null && to.intvalue!=null -> {
|
||||
@ -320,7 +320,7 @@ class AstChecker(private val globalNamespace: INameScope, private val compilerOp
|
||||
override fun process(postIncrDecr: PostIncrDecr): IStatement {
|
||||
if(postIncrDecr.target.register==null) {
|
||||
val targetName = postIncrDecr.target.identifier!!.nameInSource
|
||||
val target = globalNamespace.lookup(targetName, postIncrDecr)
|
||||
val target = namespace.lookup(targetName, postIncrDecr)
|
||||
if(target==null) {
|
||||
checkResult.add(SyntaxError("undefined symbol: ${targetName.joinToString(".")}", postIncrDecr.position))
|
||||
} else {
|
||||
@ -335,7 +335,7 @@ class AstChecker(private val globalNamespace: INameScope, private val compilerOp
|
||||
}
|
||||
|
||||
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: IStatement): IStatement? {
|
||||
val targetStatement = target.targetStatement(globalNamespace)
|
||||
val targetStatement = target.targetStatement(namespace)
|
||||
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
|
||||
return targetStatement
|
||||
checkResult.add(SyntaxError("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position))
|
||||
@ -396,7 +396,7 @@ class AstChecker(private val globalNamespace: INameScope, private val compilerOp
|
||||
val number = (av as LiteralValue).intvalue
|
||||
?: return err("array must be all bytes")
|
||||
|
||||
val expectedSize = arrayspec?.x?.constValue(globalNamespace)?.intvalue
|
||||
val expectedSize = arrayspec?.x?.constValue(namespace)?.intvalue
|
||||
if (value.arrayvalue.size != expectedSize)
|
||||
return err("initializer array size mismatch (expecting $expectedSize, got ${value.arrayvalue.size})")
|
||||
|
||||
@ -417,7 +417,7 @@ class AstChecker(private val globalNamespace: INameScope, private val compilerOp
|
||||
val number = (av as LiteralValue).intvalue
|
||||
?: return err("array must be all words")
|
||||
|
||||
val expectedSize = arrayspec?.x?.constValue(globalNamespace)?.intvalue
|
||||
val expectedSize = arrayspec?.x?.constValue(namespace)?.intvalue
|
||||
if (value.arrayvalue.size != expectedSize)
|
||||
return err("initializer array size mismatch (expecting $expectedSize, got ${value.arrayvalue.size})")
|
||||
|
||||
|
@ -88,7 +88,7 @@ class DirectedGraph<VT> {
|
||||
}
|
||||
|
||||
|
||||
class AstRecursionChecker(val namespace: INameScope) : IAstProcessor {
|
||||
class AstRecursionChecker(private val namespace: INameScope) : IAstProcessor {
|
||||
private val callGraph = DirectedGraph<INameScope>()
|
||||
|
||||
fun result(): List<AstException> {
|
||||
|
@ -3,13 +3,22 @@ package il65.stackvm
|
||||
import javax.swing.*
|
||||
import java.awt.*
|
||||
import java.awt.image.BufferedImage
|
||||
import java.util.*
|
||||
import javax.swing.Timer
|
||||
|
||||
|
||||
class MyJPanel : JPanel() {
|
||||
class BitmapScreenPanel : JPanel() {
|
||||
|
||||
var image = BufferedImage(BitmapWidth + BorderWidth * 2, BitmapHeight + BorderWidth * 2, BufferedImage.TYPE_INT_ARGB)
|
||||
private val image = BufferedImage(ScreenWidth, ScreenHeight, BufferedImage.TYPE_INT_ARGB)
|
||||
private val g2d = image.graphics as Graphics2D
|
||||
|
||||
|
||||
init {
|
||||
val size = Dimension(image.width * Scaling, image.height * Scaling)
|
||||
minimumSize = size
|
||||
maximumSize = size
|
||||
preferredSize = size
|
||||
clearScreen(6)
|
||||
}
|
||||
|
||||
override fun paint(graphics: Graphics?) {
|
||||
val g2d = graphics as Graphics2D?
|
||||
@ -19,29 +28,53 @@ class MyJPanel : JPanel() {
|
||||
g2d.drawImage(image, 0, 0, image.width * 3, image.height * 3, null)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun clearScreen(color: Int) {
|
||||
g2d.background = palette[color and 15]
|
||||
g2d.clearRect(0, 0, BitmapScreenPanel.ScreenWidth, BitmapScreenPanel.ScreenHeight)
|
||||
}
|
||||
fun setPixel(x: Int, y: Int, color: Int) {
|
||||
image.setRGB(x, y, palette[color and 15].rgb)
|
||||
}
|
||||
fun writeText(x: Int, y: Int, text: String, color: Int) {
|
||||
g2d.font = Font(Font.MONOSPACED, Font.PLAIN, 10)
|
||||
g2d.color = palette[color and 15]
|
||||
g2d.drawString(text, x, y + g2d.font.size - 1)
|
||||
}
|
||||
|
||||
const val BitmapWidth = 320
|
||||
const val BitmapHeight = 200
|
||||
const val BorderWidth = 16
|
||||
companion object {
|
||||
const val ScreenWidth = 320
|
||||
const val ScreenHeight = 256
|
||||
const val Scaling = 3
|
||||
val palette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
|
||||
Color(0x000000), // 0 = black
|
||||
Color(0xFFFFFF), // 1 = white
|
||||
Color(0x813338), // 2 = red
|
||||
Color(0x75cec8), // 3 = cyan
|
||||
Color(0x8e3c97), // 4 = purple
|
||||
Color(0x56ac4d), // 5 = green
|
||||
Color(0x2e2c9b), // 6 = blue
|
||||
Color(0xedf171), // 7 = yellow
|
||||
Color(0x8e5029), // 8 = orange
|
||||
Color(0x553800), // 9 = brown
|
||||
Color(0xc46c71), // 10 = light red
|
||||
Color(0x4a4a4a), // 11 = dark grey
|
||||
Color(0x7b7b7b), // 12 = medium grey
|
||||
Color(0xa9ff9f), // 13 = light green
|
||||
Color(0x706deb), // 14 = light blue
|
||||
Color(0xb2b2b2) // 15 = light grey
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ScreenDialog : JFrame() {
|
||||
private val canvas = MyJPanel()
|
||||
val canvas = BitmapScreenPanel()
|
||||
private val buttonQuit = JButton("Quit")
|
||||
private val logicTimer: Timer
|
||||
private val repaintTimer: Timer
|
||||
|
||||
init {
|
||||
|
||||
val screenSize = Dimension(canvas.image.width * Visuals.Scaling, canvas.image.height * Visuals.Scaling)
|
||||
val borderWidth = 16
|
||||
title = "StackVm graphics. Text I/O goes to console."
|
||||
layout = GridBagLayout()
|
||||
canvas.minimumSize = screenSize
|
||||
canvas.maximumSize = screenSize
|
||||
canvas.preferredSize = screenSize
|
||||
|
||||
defaultCloseOperation = JFrame.EXIT_ON_CLOSE
|
||||
isResizable = false
|
||||
|
||||
@ -49,85 +82,60 @@ class ScreenDialog : JFrame() {
|
||||
buttonBar.add(buttonQuit)
|
||||
|
||||
var c = GridBagConstraints()
|
||||
c.fill = GridBagConstraints.BOTH
|
||||
// the button bar
|
||||
c.gridx = 0
|
||||
c.gridy = 0
|
||||
add(canvas, c)
|
||||
c = GridBagConstraints()
|
||||
c.gridx = 0
|
||||
c.gridy = 1
|
||||
c.gridwidth = 3
|
||||
c.anchor = GridBagConstraints.LINE_END
|
||||
add(buttonBar, c)
|
||||
|
||||
getRootPane().defaultButton = buttonQuit
|
||||
buttonQuit.addActionListener { e -> onOK() }
|
||||
|
||||
|
||||
val logic = ScreenDialog.Logic()
|
||||
val visuals = Visuals(logic, canvas.image)
|
||||
|
||||
logicTimer = Timer(1000 / 120) { actionEvent -> logic.update() }
|
||||
repaintTimer = Timer(1000 / 60) { actionEvent ->
|
||||
visuals.update()
|
||||
repaint()
|
||||
// the borders (top, left, right, bottom)
|
||||
val borderTop = JPanel().apply {
|
||||
preferredSize = Dimension(BitmapScreenPanel.Scaling * (BitmapScreenPanel.ScreenWidth+2*borderWidth), BitmapScreenPanel.Scaling * borderWidth)
|
||||
background = BitmapScreenPanel.palette[14]
|
||||
}
|
||||
val borderBottom = JPanel().apply {
|
||||
preferredSize =Dimension(BitmapScreenPanel.Scaling * (BitmapScreenPanel.ScreenWidth+2*borderWidth), BitmapScreenPanel.Scaling * borderWidth)
|
||||
background = BitmapScreenPanel.palette[14]
|
||||
}
|
||||
val borderLeft = JPanel().apply {
|
||||
preferredSize =Dimension(BitmapScreenPanel.Scaling * borderWidth, BitmapScreenPanel.Scaling * BitmapScreenPanel.ScreenHeight)
|
||||
background = BitmapScreenPanel.palette[14]
|
||||
}
|
||||
val borderRight = JPanel().apply {
|
||||
preferredSize =Dimension(BitmapScreenPanel.Scaling * borderWidth, BitmapScreenPanel.Scaling * BitmapScreenPanel.ScreenHeight)
|
||||
background = BitmapScreenPanel.palette[14]
|
||||
}
|
||||
c = GridBagConstraints()
|
||||
c.gridx=0; c.gridy=1; c.gridwidth=3
|
||||
add(borderTop, c)
|
||||
c = GridBagConstraints()
|
||||
c.gridx=0; c.gridy=2
|
||||
add(borderLeft, c)
|
||||
c = GridBagConstraints()
|
||||
c.gridx=2; c.gridy=2
|
||||
add(borderRight, c)
|
||||
c = GridBagConstraints()
|
||||
c.gridx=0; c.gridy=3; c.gridwidth=3
|
||||
add(borderBottom, c)
|
||||
// the screen canvas(bitmap)
|
||||
c = GridBagConstraints()
|
||||
c.gridx = 1; c.gridy = 2
|
||||
add(canvas, c)
|
||||
|
||||
logicTimer.start()
|
||||
getRootPane().defaultButton = buttonQuit
|
||||
buttonQuit.addActionListener { _ -> onOK() }
|
||||
|
||||
}
|
||||
|
||||
fun start() {
|
||||
val repaintTimer = Timer(1000 / 60) { _ -> repaint() }
|
||||
repaintTimer.start()
|
||||
}
|
||||
|
||||
private fun onOK() {
|
||||
// add your code here
|
||||
dispose()
|
||||
logicTimer.stop()
|
||||
repaintTimer.stop()
|
||||
System.exit(0)
|
||||
}
|
||||
|
||||
internal class Logic {
|
||||
var last = System.currentTimeMillis()
|
||||
|
||||
fun update() {
|
||||
val now = System.currentTimeMillis()
|
||||
val timeSinceLast = now - last
|
||||
last = now
|
||||
}
|
||||
}
|
||||
|
||||
internal class Visuals(private val logic: Logic, private val image: BufferedImage) {
|
||||
private val rnd = Random()
|
||||
companion object {
|
||||
const val Scaling = 3
|
||||
}
|
||||
|
||||
init {
|
||||
val g2d = image.graphics as Graphics2D
|
||||
g2d.background = Color(0x5533ff)
|
||||
g2d.clearRect(0, 0, image.width, image.height)
|
||||
g2d.background = Color(0x222277)
|
||||
g2d.clearRect(MyJPanel.BorderWidth, MyJPanel.BorderWidth, MyJPanel.BitmapWidth, MyJPanel.BitmapHeight)
|
||||
g2d.font = Font(Font.MONOSPACED, Font.PLAIN, 12)
|
||||
g2d.drawString("HELLO world 12345", 100, 100)
|
||||
}
|
||||
|
||||
fun update() {
|
||||
|
||||
image.setRGB(rnd.nextInt(MyJPanel.BitmapWidth), rnd.nextInt(MyJPanel.BitmapHeight), rnd.nextInt())
|
||||
|
||||
// for(int x = 50; x < MyJPanel.BitmapWidth-50; x++) {
|
||||
// for(int y = 50; y < MyJPanel.BitmapHeight-50; y++) {
|
||||
// image.setRGB(x+MyJPanel.BorderWidth, y+MyJPanel.BorderWidth, rnd.nextInt());
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
EventQueue.invokeLater {
|
||||
val dialog = ScreenDialog()
|
||||
dialog.pack()
|
||||
dialog.isVisible = true
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,12 @@ package il65.stackvm
|
||||
import il65.ast.DataType
|
||||
import il65.compiler.Mflpt5
|
||||
import il65.compiler.Petscii
|
||||
import java.awt.EventQueue
|
||||
import java.io.File
|
||||
import java.io.PrintWriter
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
import javax.swing.Timer
|
||||
import kotlin.math.max
|
||||
import kotlin.math.pow
|
||||
|
||||
@ -14,11 +16,14 @@ enum class Opcode {
|
||||
|
||||
// pushing values on the (evaluation) stack
|
||||
PUSH, // push constant byte value
|
||||
PUSH_MEM, // push byte value from memory to stack
|
||||
PUSH_MEM_W, // push word value from memory to stack
|
||||
PUSH_MEM_F, // push float value from memory to stack
|
||||
PUSH_VAR, // push a variable
|
||||
DUP, // push topmost value once again
|
||||
|
||||
// popping values off the (evaluation) stack, possibly storing them in another location
|
||||
DISCARD, // discard X bytes from the top of the stack
|
||||
DISCARD, // discard top value
|
||||
POP_MEM, // pop value into destination memory address
|
||||
POP_VAR, // pop value into variable
|
||||
|
||||
@ -78,12 +83,12 @@ enum class Opcode {
|
||||
JUMP,
|
||||
BCS,
|
||||
BCC,
|
||||
//BEQ, // @todo not implemented status flag Z
|
||||
//BNE, // @todo not implemented status flag Z
|
||||
//BVS, // @todo not implemented status flag V
|
||||
//BVC, // @todo not implemented status flag V
|
||||
//BMI, // @todo not implemented status flag N
|
||||
//BPL, // @todo not implemented status flag N
|
||||
BEQ, // branch if value on top of stack is zero
|
||||
BNE, // branch if value on top of stack is not zero
|
||||
BMI, // branch if value on top of stack < 0
|
||||
BPL, // branch if value on top of stack >= 0
|
||||
// BVS, // status flag V (overflow) not implemented
|
||||
// BVC, // status flag V (overflow) not implemented
|
||||
|
||||
// subroutine calling
|
||||
CALL,
|
||||
@ -92,8 +97,8 @@ enum class Opcode {
|
||||
|
||||
// misc
|
||||
SWAP,
|
||||
SEC,
|
||||
CLC,
|
||||
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,
|
||||
TERMINATE
|
||||
}
|
||||
@ -105,6 +110,11 @@ enum class Syscall(val callNr: Short) {
|
||||
WRITE_CHAR(13), // pop from the evaluation stack and print it as a single petscii character
|
||||
WRITE_VAR(14), // print the number or string from the given variable
|
||||
INPUT_VAR(15), // user input a string into a variable
|
||||
GFX_PIXEL(16), // plot a pixel at (x,y,color) pushed on stack in that order
|
||||
GFX_CLEARSCR(17), // clear the screen with color pushed on stack
|
||||
GFX_TEXT(18), // write text on screen at (x,y,text,color) pushed on stack in that order
|
||||
RANDOM(19), // push a random byte on the stack
|
||||
RANDOM_W(20) // push a random word on the stack
|
||||
}
|
||||
|
||||
class Memory {
|
||||
@ -506,17 +516,15 @@ class Program (prog: MutableList<Instruction>,
|
||||
private fun getArgValue(args: String?): Value? {
|
||||
if(args==null)
|
||||
return null
|
||||
if(args[0]=='"' && args[args.length-1]=='"') {
|
||||
// it's a string.
|
||||
return Value(DataType.STR, null, unescape(args.substring(1, args.length-1)))
|
||||
}
|
||||
val (type, valueStr) = args.split(':')
|
||||
return when(type) {
|
||||
"byte" -> Value(DataType.BYTE, valueStr.toShort(16))
|
||||
"word" -> Value(DataType.WORD, valueStr.toInt(16))
|
||||
"float" -> Value(DataType.FLOAT, valueStr.toDouble())
|
||||
"str" -> {
|
||||
if(valueStr.startsWith('"') && valueStr.endsWith('"'))
|
||||
Value(DataType.STR, null, unescape(valueStr.substring(1, valueStr.length-1)))
|
||||
else
|
||||
throw VmExecutionException("str should be enclosed in quotes")
|
||||
}
|
||||
"b" -> Value(DataType.BYTE, valueStr.toShort(16))
|
||||
"w" -> Value(DataType.WORD, valueStr.toInt(16))
|
||||
"f" -> Value(DataType.FLOAT, valueStr.toDouble())
|
||||
else -> throw VmExecutionException("invalid datatype $type")
|
||||
}
|
||||
}
|
||||
@ -616,13 +624,18 @@ class Program (prog: MutableList<Instruction>,
|
||||
when(instr.opcode) {
|
||||
Opcode.TERMINATE -> instr.next = instr // won't ever execute a next instruction
|
||||
Opcode.RETURN -> instr.next = instr // kinda a special one, in actuality the return instruction is dynamic
|
||||
Opcode.JUMP, Opcode.BCC, Opcode.BCS -> {
|
||||
Opcode.JUMP -> {
|
||||
val target = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
|
||||
instr.next = target
|
||||
}
|
||||
Opcode.BCC, Opcode.BCS, Opcode.BEQ, Opcode.BNE, Opcode.BMI, Opcode.BPL -> {
|
||||
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
|
||||
instr.next = jumpInstr
|
||||
instr.nextAlt = nextInstr
|
||||
}
|
||||
Opcode.CALL -> {
|
||||
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
|
||||
instr.next=jumpInstr
|
||||
instr.next = jumpInstr
|
||||
instr.nextAlt = nextInstr // instruction to return to
|
||||
}
|
||||
else -> instr.next = nextInstr
|
||||
@ -633,33 +646,41 @@ class Program (prog: MutableList<Instruction>,
|
||||
|
||||
|
||||
class StackVm(val traceOutputFile: String?) {
|
||||
val mem = Memory()
|
||||
private val mem = Memory()
|
||||
private val evalstack = MyStack<Value>() // evaluation stack
|
||||
private val callstack = MyStack<Instruction>() // subroutine call stack (@todo maybe use evalstack as well for this?)
|
||||
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 lateinit var currentIns: Instruction
|
||||
private lateinit var canvas: BitmapScreenPanel
|
||||
private val rnd = Random()
|
||||
|
||||
fun run(program: Program) {
|
||||
fun load(program: Program, canvas: BitmapScreenPanel) {
|
||||
this.program = program.program
|
||||
this.canvas = canvas
|
||||
this.variables = program.variables.toMutableMap()
|
||||
initMemory(program.memory)
|
||||
var ins = this.program[0]
|
||||
currentIns = this.program[0]
|
||||
}
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
ins = dispatch(ins)
|
||||
fun step() {
|
||||
// step is invoked every 1/100 sec
|
||||
// we execute 5000 instructions in one go so we end up doing 500.000 instructions per second
|
||||
val instructionsPerStep = 5000
|
||||
val start = System.currentTimeMillis()
|
||||
for(i:Int in 0..instructionsPerStep) {
|
||||
currentIns = dispatch(currentIns)
|
||||
|
||||
if(evalstack.size > 128)
|
||||
throw VmExecutionException("too many values on evaluation stack")
|
||||
if(callstack.size > 128)
|
||||
throw VmExecutionException("too many nested/recursive calls")
|
||||
}
|
||||
} catch (x: VmTerminationException) {
|
||||
println("\n\nExecution terminated.")
|
||||
} finally {
|
||||
traceOutput?.close()
|
||||
if (evalstack.size > 128)
|
||||
throw VmExecutionException("too many values on evaluation stack")
|
||||
if (callstack.size > 128)
|
||||
throw VmExecutionException("too many nested/recursive calls")
|
||||
}
|
||||
val time = System.currentTimeMillis()-start
|
||||
if(time > 100) {
|
||||
println("WARNING: vm dispatch step took > 100 msec")
|
||||
}
|
||||
}
|
||||
|
||||
@ -695,6 +716,18 @@ class StackVm(val traceOutputFile: String?) {
|
||||
when (ins.opcode) {
|
||||
Opcode.NOP -> {}
|
||||
Opcode.PUSH -> evalstack.push(ins.arg)
|
||||
Opcode.PUSH_MEM -> {
|
||||
val address = ins.arg!!.integerValue()
|
||||
evalstack.push(Value(DataType.BYTE, mem.getByte(address)))
|
||||
}
|
||||
Opcode.PUSH_MEM_W -> {
|
||||
val address = ins.arg!!.integerValue()
|
||||
evalstack.push(Value(DataType.WORD, mem.getWord(address)))
|
||||
}
|
||||
Opcode.PUSH_MEM_F -> {
|
||||
val address = ins.arg!!.integerValue()
|
||||
evalstack.push(Value(DataType.FLOAT, mem.getFloat(address)))
|
||||
}
|
||||
Opcode.DUP -> evalstack.push(evalstack.peek())
|
||||
Opcode.DISCARD -> evalstack.pop()
|
||||
Opcode.SWAP -> {
|
||||
@ -818,6 +851,24 @@ class StackVm(val traceOutputFile: String?) {
|
||||
}
|
||||
variables[varname] = value
|
||||
}
|
||||
Syscall.GFX_PIXEL -> {
|
||||
// plot pixel at (x, y, color) from stack
|
||||
val color = evalstack.pop()
|
||||
val (y, x) = evalstack.pop2()
|
||||
canvas.setPixel(x.integerValue(), y.integerValue(), color.integerValue())
|
||||
}
|
||||
Syscall.GFX_CLEARSCR -> {
|
||||
val color = evalstack.pop()
|
||||
canvas.clearScreen(color.integerValue())
|
||||
}
|
||||
Syscall.GFX_TEXT -> {
|
||||
val color = evalstack.pop()
|
||||
val text = evalstack.pop()
|
||||
val (y, x) = evalstack.pop2()
|
||||
canvas.writeText(x.integerValue(), y.integerValue(), text.stringvalue!!, color.integerValue())
|
||||
}
|
||||
Syscall.RANDOM -> evalstack.push(Value(DataType.BYTE, rnd.nextInt() and 255))
|
||||
Syscall.RANDOM_W -> evalstack.push(Value(DataType.WORD, rnd.nextInt() and 65535))
|
||||
else -> throw VmExecutionException("unimplemented syscall $syscall")
|
||||
}
|
||||
}
|
||||
@ -926,6 +977,10 @@ class StackVm(val traceOutputFile: String?) {
|
||||
Opcode.JUMP -> {} // do nothing; the next instruction is wired up already to the jump target
|
||||
Opcode.BCS -> return if(carry) ins.next else ins.nextAlt!!
|
||||
Opcode.BCC -> return if(carry) ins.nextAlt!! else ins.next
|
||||
Opcode.BEQ -> return if(evalstack.peek().numericValue().toDouble()==0.0) ins.next else ins.nextAlt!!
|
||||
Opcode.BNE -> return if(evalstack.peek().numericValue().toDouble()!=0.0) ins.next else ins.nextAlt!!
|
||||
Opcode.BMI -> return if(evalstack.peek().numericValue().toDouble()<0.0) ins.next else ins.nextAlt!!
|
||||
Opcode.BPL -> return if(evalstack.peek().numericValue().toDouble()>=0.0) ins.next else ins.nextAlt!!
|
||||
Opcode.CALL -> callstack.push(ins.nextAlt)
|
||||
Opcode.RETURN -> return callstack.pop()
|
||||
Opcode.PUSH_VAR -> {
|
||||
@ -1005,7 +1060,16 @@ class StackVm(val traceOutputFile: String?) {
|
||||
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val vm = StackVm(traceOutputFile = "vmtrace.txt")
|
||||
val program = Program.load("il65/examples/stackvmtest.txt")
|
||||
vm.run(program)
|
||||
}
|
||||
val program = Program.load("examples/stackvmtest.txt")
|
||||
val vm = StackVm(traceOutputFile = null)
|
||||
val dialog = ScreenDialog()
|
||||
vm.load(program, dialog.canvas)
|
||||
EventQueue.invokeLater {
|
||||
dialog.pack()
|
||||
dialog.isVisible = true
|
||||
dialog.start()
|
||||
|
||||
val programTimer = Timer(10) { _ -> vm.step() }
|
||||
programTimer.start()
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user