screen added to vm, some new opcodes

This commit is contained in:
Irmen de Jong 2018-09-10 22:39:15 +02:00
parent af7103f0f7
commit e71fef709a
6 changed files with 216 additions and 146 deletions

View File

@ -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

View File

@ -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)"
}

View File

@ -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})")

View File

@ -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> {

View File

@ -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
}
}

View File

@ -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()
}
}