first go at ast-based virtual machine (rather than the stackvm that uses intermediate code)

This commit is contained in:
Irmen de Jong 2019-06-23 22:08:48 +02:00
parent 99121004bf
commit e53c860f1a
11 changed files with 1448 additions and 4 deletions

124
.idea/uiDesigner.xml generated Normal file
View File

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

View File

@ -1,6 +1,7 @@
package prog8 package prog8
import prog8.ast.* import prog8.ast.*
import prog8.astvm.AstVm
import prog8.compiler.* import prog8.compiler.*
import prog8.compiler.target.c64.AsmGen import prog8.compiler.target.c64.AsmGen
import prog8.compiler.target.c64.C64Zeropage import prog8.compiler.target.c64.C64Zeropage
@ -49,6 +50,7 @@ private fun compileMain(args: Array<String>) {
var writeAssembly = true var writeAssembly = true
var optimize = true var optimize = true
var optimizeInlining = true var optimizeInlining = true
var launchAstVm = false
for (arg in args) { for (arg in args) {
if(arg=="-emu") if(arg=="-emu")
emulatorToStart = "x64" emulatorToStart = "x64"
@ -62,6 +64,8 @@ private fun compileMain(args: Array<String>) {
optimize = false optimize = false
else if(arg=="-nooptinline") else if(arg=="-nooptinline")
optimizeInlining = false optimizeInlining = false
else if(arg=="-avm")
launchAstVm = true
else if(!arg.startsWith("-")) else if(!arg.startsWith("-"))
moduleFile = arg moduleFile = arg
else else
@ -72,12 +76,13 @@ private fun compileMain(args: Array<String>) {
val filepath = Paths.get(moduleFile).normalize() val filepath = Paths.get(moduleFile).normalize()
var programname = "?" var programname = "?"
lateinit var programAst: Program
try { try {
val totalTime = measureTimeMillis { val totalTime = measureTimeMillis {
// import main module and everything it needs // import main module and everything it needs
println("Parsing...") println("Parsing...")
val programAst = Program(moduleName(filepath.fileName), mutableListOf()) programAst = Program(moduleName(filepath.fileName), mutableListOf())
importModule(programAst, filepath) importModule(programAst, filepath)
val compilerOptions = determineCompilationOptions(programAst) val compilerOptions = determineCompilationOptions(programAst)
@ -180,6 +185,12 @@ private fun compileMain(args: Array<String>) {
throw x throw x
} }
if(launchAstVm) {
println("\nLaunching AST-based vm...")
val vm = AstVm(programAst)
vm.run()
}
if(emulatorToStart.isNotEmpty()) { if(emulatorToStart.isNotEmpty()) {
println("\nStarting C-64 emulator $emulatorToStart...") println("\nStarting C-64 emulator $emulatorToStart...")
val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "$programname.vice-mon-list", val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "$programname.vice-mon-list",
@ -233,6 +244,7 @@ private fun usage() {
System.err.println(" [-writevm] write intermediate vm code to a file as well") System.err.println(" [-writevm] write intermediate vm code to a file as well")
System.err.println(" [-noasm] don't create assembly code") System.err.println(" [-noasm] don't create assembly code")
System.err.println(" [-vm] launch the prog8 virtual machine instead of the compiler") System.err.println(" [-vm] launch the prog8 virtual machine instead of the compiler")
System.err.println(" [-avm] launch the prog8 ast-based virtual machine after compilation")
System.err.println(" [-noopt] don't perform any optimizations") System.err.println(" [-noopt] don't perform any optimizations")
System.err.println(" [-nooptinline] don't perform subroutine inlining optimizations") System.err.println(" [-nooptinline] don't perform subroutine inlining optimizations")
System.err.println(" modulefile main module file to compile") System.err.println(" modulefile main module file to compile")

View File

@ -1597,7 +1597,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
fun heapId(namespace: INameScope): Int { fun heapId(namespace: INameScope): Int {
val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolError(this) val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolError(this)
return ((node as? VarDecl)?.value as? LiteralValue)?.heapId ?: throw FatalAstException("identifier is not on the heap") return ((node as? VarDecl)?.value as? LiteralValue)?.heapId ?: throw FatalAstException("identifier is not on the heap: $this")
} }
} }

View File

@ -0,0 +1,319 @@
package prog8.astvm
import prog8.ast.*
import java.awt.EventQueue
class VmExecutionException(msg: String?) : Exception(msg)
class VmTerminationException(msg: String?) : Exception(msg)
class VmBreakpointException : Exception("breakpoint")
class RuntimeVariables {
fun define(scope: INameScope, name: String, initialValue: RuntimeValue) {
val where = vars.getValue(scope)
where[name] = initialValue
vars[scope] = where
println("DEFINE RUNTIMEVAR: ${scope.name}.$name = $initialValue") // TODO
}
fun set(scope: INameScope, name: String, value: RuntimeValue) {
val where = vars.getValue(scope)
val existing = where[name] ?: throw NoSuchElementException("no such runtime variable: ${scope.name}.$name")
if(existing.type!=value.type)
throw VmExecutionException("new value is of different datatype ${value.type} expected ${existing.type} for $name")
where[name] = value
vars[scope] = where
println("SET RUNTIMEVAR: ${scope.name}.$name = $value") // TODO
}
fun get(scope: INameScope, name: String): RuntimeValue {
val where = vars.getValue(scope)
val value = where[name]
if(value!=null)
return value
throw NoSuchElementException("no such runtime variable: ${scope.name}.$name")
}
private val vars = mutableMapOf<INameScope, MutableMap<String, RuntimeValue>>().withDefault { mutableMapOf() }
}
class AstVm(val program: Program) {
val mem = Memory()
var P_carry: Boolean = false
private set
var P_zero: Boolean = true
private set
var P_negative: Boolean = false
private set
var P_irqd: Boolean = false
private set
private var dialog = ScreenDialog()
init {
dialog.requestFocusInWindow()
EventQueue.invokeLater {
dialog.pack()
dialog.isVisible = true
dialog.start()
}
}
fun run() {
try {
val init = VariablesInitializer(runtimeVariables, program.heap)
init.process(program)
val entrypoint = program.entrypoint() ?: throw VmTerminationException("no valid entrypoint found")
executeSubroutine(entrypoint, emptyList())
println("PROGRAM EXITED!")
dialog.title = "PROGRAM EXITED"
} catch(bp: VmBreakpointException) {
println("Breakpoint: execution halted. Press enter to resume.")
readLine()
} catch (tx: VmTerminationException) {
println("Execution halted: ${tx.message}")
} catch (xx: VmExecutionException) {
println("Execution error: ${xx.message}")
throw xx
}
}
private val runtimeVariables = RuntimeVariables()
internal fun executeSubroutine(sub: INameScope, arguments: List<RuntimeValue>): List<RuntimeValue> {
if (sub.statements.isEmpty())
throw VmTerminationException("scope contains no statements: $sub")
if(sub is Subroutine) {
assert(!sub.isAsmSubroutine)
// TODO process arguments if it's a subroutine
}
for (s in sub.statements) {
if(s is Return) {
return s.values.map { evaluate(it, program, runtimeVariables, ::executeSubroutine) }
}
executeStatement(sub, s)
}
if(sub !is AnonymousScope)
throw VmTerminationException("instruction pointer overflow, is a return missing? $sub")
return emptyList()
}
private fun executeStatement(sub: INameScope, stmt: IStatement) {
when (stmt) {
is NopStatement, is Label, is Subroutine -> {
// do nothing, skip this instruction
}
is Directive -> {
if(stmt.directive=="%breakpoint")
throw VmBreakpointException()
else if(stmt.directive=="%asm")
throw VmExecutionException("can't execute assembly code")
}
is VarDecl -> {
// should have been defined already when the program started
}
is FunctionCallStatement -> {
val target = stmt.target.targetStatement(program.namespace)
when(target) {
is Subroutine -> {
val args = evaluate(stmt.arglist)
if(target.isAsmSubroutine) {
performSyscall(target, args)
} else {
val results = executeSubroutine(target, args)
// TODO process result values
}
}
is BuiltinFunctionStatementPlaceholder -> {
val args = evaluate(stmt.arglist)
performBuiltinFunction(target.name, args)
}
else -> {
TODO("CALL $target")
}
}
}
is BuiltinFunctionStatementPlaceholder -> {
TODO("$stmt")
}
is Return -> {
throw VmExecutionException("return statement should have been handled by the subroutine loop")
}
is Continue -> {
TODO("$stmt")
}
is Break -> {
TODO("$stmt")
}
is Assignment -> {
if(stmt.aug_op==null) {
val target = stmt.singleTarget
if(target!=null) {
when {
target.identifier!=null -> {
val ident = stmt.definingScope().lookup(target.identifier.nameInSource, stmt) as VarDecl
val value = evaluate(stmt.value, program, runtimeVariables, ::executeSubroutine)
val identScope = ident.definingScope()
runtimeVariables.set(identScope, ident.name, value)
}
target.memoryAddress!=null -> {
TODO("$stmt")
}
target.arrayindexed!=null -> {
val array = evaluate(target.arrayindexed.identifier, program, runtimeVariables, ::executeSubroutine)
val index = evaluate(target.arrayindexed.arrayspec.index, program, runtimeVariables, ::executeSubroutine)
val value = evaluate(stmt.value, program, runtimeVariables, ::executeSubroutine)
when(array.type) {
DataType.ARRAY_UB -> {
if(value.type!=DataType.UBYTE)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
DataType.ARRAY_B -> {
if(value.type!=DataType.BYTE)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
DataType.ARRAY_UW -> {
if(value.type!=DataType.UWORD)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
DataType.ARRAY_W -> {
if(value.type!=DataType.WORD)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
DataType.ARRAY_F -> {
if(value.type!=DataType.FLOAT)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
else -> throw VmExecutionException("strange array type ${array.type}")
}
array.array!![index.integerValue()] = value.numericValue()
}
}
}
else TODO("$stmt")
} else TODO("$stmt")
}
is PostIncrDecr -> {
TODO("$stmt")
}
is Jump -> {
TODO("$stmt")
}
is InlineAssembly -> {
throw VmExecutionException("can't execute inline assembly in $sub")
}
is AnonymousScope -> {
throw VmExecutionException("anonymous scopes should have been flattened")
}
is IfStatement -> {
TODO("$stmt")
}
is BranchStatement -> {
TODO("$stmt")
}
is ForLoop -> {
TODO("$stmt")
}
is WhileLoop -> {
var condition = evaluate(stmt.condition, program, runtimeVariables, ::executeSubroutine)
while(condition.asBooleanRuntimeValue) {
println("STILL IN WHILE LOOP ${stmt.position}")
executeSubroutine(stmt.body, emptyList())
condition = evaluate(stmt.condition, program, runtimeVariables, ::executeSubroutine)
}
println(">>>>WHILE LOOP EXITED")
}
is RepeatLoop -> {
do {
val condition = evaluate(stmt.untilCondition, program, runtimeVariables, ::executeSubroutine)
executeSubroutine(stmt.body, emptyList())
} while(!condition.asBooleanRuntimeValue)
}
else -> {
TODO("implement $stmt")
}
}
}
private fun evaluate(args: List<IExpression>): List<RuntimeValue> = args.map { evaluate(it, program, runtimeVariables, ::executeSubroutine) }
private fun performBuiltinFunction(name: String, args: List<RuntimeValue>) {
when(name) {
"memset" -> {
val target = args[0].array!!
val amount = args[1].integerValue()
val value = args[2].integerValue()
for(i in 0 until amount) {
target[i] = value
}
}
else -> TODO("builtin function $name")
}
}
private fun performSyscall(sub: Subroutine, args: List<RuntimeValue>) {
assert(sub.isAsmSubroutine)
when(sub.scopedname) {
"c64scr.print" -> {
// if the argument is an UWORD, consider it to be the "address" of the string (=heapId)
if(args[0].wordval!=null) {
val str = program.heap.get(args[0].wordval!!).str!!
dialog.canvas.printText(str, 1, true)
}
else
dialog.canvas.printText(args[0].str!!, 1, true)
}
"c64scr.print_ub" -> {
dialog.canvas.printText(args[0].byteval!!.toString(), 1, true)
}
"c64scr.print_uw" -> {
dialog.canvas.printText(args[0].wordval!!.toString(), 1, true)
}
"c64.CHROUT" -> {
dialog.canvas.printChar(args[0].byteval!!)
}
else -> TODO("syscall $sub")
}
}
private fun setFlags(value: LiteralValue?) {
if(value!=null) {
when(value.type) {
DataType.UBYTE -> {
val v = value.bytevalue!!.toInt()
P_negative = v>127
P_zero = v==0
}
DataType.BYTE -> {
val v = value.bytevalue!!.toInt()
P_negative = v<0
P_zero = v==0
}
DataType.UWORD -> {
val v = value.wordvalue!!
P_negative = v>32767
P_zero = v==0
}
DataType.WORD -> {
val v = value.wordvalue!!
P_negative = v<0
P_zero = v==0
}
DataType.FLOAT -> {
val flt = value.floatvalue!!
P_negative = flt < 0.0
P_zero = flt==0.0
}
else -> {
// no flags for non-numeric type
}
}
}
}
}

View File

@ -0,0 +1,18 @@
package prog8.astvm
import prog8.ast.INameScope
import java.util.*
class CallStack {
private val stack = Stack<Pair<INameScope, Int>>()
fun pop(): Pair<INameScope, Int> {
return stack.pop()
}
fun push(scope: INameScope, index: Int) {
stack.push(Pair(scope, index))
}
}

View File

@ -0,0 +1,102 @@
package prog8.astvm
import prog8.ast.*
fun evaluate(expr: IExpression, program: Program, runtimeVars: RuntimeVariables,
executeSubroutine: (sub: Subroutine, args: List<RuntimeValue>) -> List<RuntimeValue>): RuntimeValue {
val constval = expr.constValue(program)
if(constval!=null)
return RuntimeValue.from(constval, program.heap)
when(expr) {
is LiteralValue -> {
return RuntimeValue.from(expr, program.heap)
}
is PrefixExpression -> {
TODO("$expr")
}
is BinaryExpression -> {
val left = evaluate(expr.left, program, runtimeVars, executeSubroutine)
val right = evaluate(expr.right, program, runtimeVars, executeSubroutine)
return when(expr.operator) {
"<" -> RuntimeValue(DataType.UBYTE, if(left < right) 1 else 0)
"<=" -> RuntimeValue(DataType.UBYTE, if(left <= right) 1 else 0)
">" -> RuntimeValue(DataType.UBYTE, if(left > right) 1 else 0)
">=" -> RuntimeValue(DataType.UBYTE, if(left >= right) 1 else 0)
"==" -> RuntimeValue(DataType.UBYTE, if(left == right) 1 else 0)
"!=" -> RuntimeValue(DataType.UBYTE, if(left != right) 1 else 0)
"+" -> {
val result = left.add(right)
RuntimeValue(result.type, result.numericValue())
}
"-" -> {
val result = left.sub(right)
RuntimeValue(result.type, result.numericValue())
}
else -> TODO("binexpression operator ${expr.operator}")
}
}
is ArrayIndexedExpression -> {
val array = evaluate(expr.identifier, program, runtimeVars, executeSubroutine)
val index = evaluate(expr.arrayspec.index, program, runtimeVars, executeSubroutine)
val value = array.array!![index.integerValue()]
return when(array.type) {
DataType.ARRAY_UB -> RuntimeValue(DataType.UBYTE, num=value)
DataType.ARRAY_B -> RuntimeValue(DataType.BYTE, num=value)
DataType.ARRAY_UW -> RuntimeValue(DataType.UWORD, num=value)
DataType.ARRAY_W -> RuntimeValue(DataType.WORD, num=value)
DataType.ARRAY_F -> RuntimeValue(DataType.FLOAT, num=value)
else -> throw VmExecutionException("strange array type ${array.type}")
}
}
is TypecastExpression -> {
return evaluate(expr.expression, program, runtimeVars, executeSubroutine).cast(expr.type)
}
is AddressOf -> {
// we support: address of heap var -> the heap id
val heapId = expr.identifier.heapId(program.namespace)
return RuntimeValue(DataType.UWORD, num=heapId)
}
is DirectMemoryRead -> {
TODO("$expr")
}
is DirectMemoryWrite -> {
TODO("$expr")
}
is RegisterExpr -> {
TODO("$expr")
}
is IdentifierReference -> {
val scope = expr.definingScope()
val variable = scope.lookup(expr.nameInSource, expr)
if(variable is VarDecl) {
val stmt = scope.lookup(listOf(variable.name), expr)!!
return runtimeVars.get(stmt.definingScope(), variable.name)
} else
TODO("$variable")
}
is FunctionCall -> {
val sub = expr.target.targetStatement(program.namespace)
val args = expr.arglist.map { evaluate(it, program, runtimeVars, executeSubroutine) }
when(sub) {
is Subroutine -> {
val results = executeSubroutine(sub, args)
if(results.size!=1)
throw VmExecutionException("expected 1 result from functioncall $expr")
return results[0]
}
else -> {
TODO("call expr function ${expr.target}")
}
}
}
is RangeExpr -> {
TODO("eval range $expr")
}
else -> {
TODO("implement eval $expr")
}
}
}

View File

@ -0,0 +1,102 @@
package prog8.astvm
import prog8.compiler.target.c64.Mflpt5
import prog8.compiler.target.c64.Petscii
import kotlin.math.abs
class Memory {
private val mem = ShortArray(65536) // shorts because byte is signed and we store values 0..255
fun getUByte(address: Int): Short {
return mem[address]
}
fun getSByte(address: Int): Short {
val ubyte = getUByte(address)
if(ubyte <= 127)
return ubyte
return (-((ubyte.toInt() xor 255)+1)).toShort() // 2's complement
}
fun setUByte(address: Int, value: Short) {
if(value !in 0..255)
throw VmExecutionException("ubyte value out of range")
mem[address] = value
}
fun setSByte(address: Int, value: Short) {
if(value !in -128..127) throw VmExecutionException("byte value out of range")
if(value>=0)
mem[address] = value
else
mem[address] = ((abs(value.toInt()) xor 255)+1).toShort() // 2's complement
}
fun getUWord(address: Int): Int {
return mem[address] + 256*mem[address+1]
}
fun getSWord(address: Int): Int {
val uword = getUWord(address)
if(uword <= 32767)
return uword
return -((uword xor 65535)+1) // 2's complement
}
fun setUWord(address: Int, value: Int) {
if(value !in 0..65535)
throw VmExecutionException("uword value out of range")
mem[address] = value.and(255).toShort()
mem[address+1] = (value / 256).toShort()
}
fun setSWord(address: Int, value: Int) {
if(value !in -32768..32767) throw VmExecutionException("word value out of range")
if(value>=0)
setUWord(address, value)
else
setUWord(address, (abs(value) xor 65535)+1) // 2's complement
}
fun setFloat(address: Int, value: Double) {
val mflpt5 = Mflpt5.fromNumber(value)
mem[address] = mflpt5.b0
mem[address+1] = mflpt5.b1
mem[address+2] = mflpt5.b2
mem[address+3] = mflpt5.b3
mem[address+4] = mflpt5.b4
}
fun getFloat(address: Int): Double {
return Mflpt5(mem[address], mem[address + 1], mem[address + 2], mem[address + 3], mem[address + 4]).toDouble()
}
fun setString(address: Int, str: String) {
// lowercase PETSCII
val petscii = Petscii.encodePetscii(str, true)
var addr = address
for (c in petscii) mem[addr++] = c
mem[addr] = 0
}
fun getString(strAddress: Int): String {
// lowercase PETSCII
val petscii = mutableListOf<Short>()
var addr = strAddress
while(true) {
val byte = mem[addr++]
if(byte==0.toShort()) break
petscii.add(byte)
}
return Petscii.decodePetscii(petscii, true)
}
fun clear() {
for(i in 0..65535) mem[i]=0
}
fun copy(from: Int, to: Int, numbytes: Int) {
for(i in 0 until numbytes)
mem[to+i] = mem[from+i]
}
}

View File

@ -0,0 +1,516 @@
package prog8.astvm
import prog8.ast.*
import prog8.compiler.HeapValues
import kotlin.math.abs
import kotlin.math.pow
class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=null, val array: Array<Number>?=null, val heapId: Int?=null) {
val byteval: Short?
val wordval: Int?
val floatval: Double?
val asBooleanRuntimeValue: Boolean
companion object {
fun from(literalValue: LiteralValue, heap: HeapValues): RuntimeValue {
return when(literalValue.type) {
in NumericDatatypes -> RuntimeValue(literalValue.type, num = literalValue.asNumericValue!!)
in StringDatatypes -> from(literalValue.heapId!!, heap)
in ArrayDatatypes -> from(literalValue.heapId!!, heap)
else -> TODO("type")
}
}
fun from(heapId: Int, heap: HeapValues): RuntimeValue {
val value = heap.get(heapId)
return when {
value.type in StringDatatypes ->
RuntimeValue(value.type, str = value.str!!, heapId = heapId)
value.type in ArrayDatatypes ->
if (value.type == DataType.ARRAY_F) {
RuntimeValue(value.type, array = value.doubleArray!!.toList().toTypedArray(), heapId = heapId)
} else {
val array = value.array!!
if (array.any { it.addressOf != null })
TODO("addressof values")
RuntimeValue(value.type, array = array.map { it.integer!! }.toTypedArray(), heapId = heapId)
}
else -> TODO("weird type on heap")
}
}
}
init {
when(type) {
DataType.UBYTE -> {
byteval = num!!.toShort()
if(byteval !in 0..255)
throw ArithmeticException("value out of range: $num")
wordval = null
floatval = null
asBooleanRuntimeValue = num != 0
}
DataType.BYTE -> {
byteval = num!!.toShort()
if(byteval !in -128..127)
throw ArithmeticException("value out of range: $num")
wordval = null
floatval = null
asBooleanRuntimeValue = num != 0
}
DataType.UWORD -> {
wordval = num!!.toInt()
if(wordval !in 0..65535)
throw ArithmeticException("value out of range: $num")
byteval = null
floatval = null
asBooleanRuntimeValue = wordval != 0
}
DataType.WORD -> {
wordval = num!!.toInt()
if(wordval !in -32768..32767)
throw ArithmeticException("value out of range: $num")
byteval = null
floatval = null
asBooleanRuntimeValue = wordval != 0
}
DataType.FLOAT -> {
floatval = num!!.toDouble()
byteval = null
wordval = null
asBooleanRuntimeValue = floatval != 0.0
}
else -> {
if(heapId==null)
throw ArithmeticException("for non-numeric types, a heapId should be given")
byteval = null
wordval = null
floatval = null
asBooleanRuntimeValue = true
}
}
}
override fun toString(): String {
return when(type) {
DataType.UBYTE -> "ub:%02x".format(byteval)
DataType.BYTE -> {
if(byteval!!<0)
"b:-%02x".format(abs(byteval!!.toInt()))
else
"b:%02x".format(byteval)
}
DataType.UWORD -> "uw:%04x".format(wordval)
DataType.WORD -> {
if(wordval!!<0)
"w:-%04x".format(abs(wordval!!))
else
"w:%04x".format(wordval)
}
DataType.FLOAT -> "f:$floatval"
else -> "heap:$heapId"
}
}
fun numericValue(): Number {
return when(type) {
in ByteDatatypes -> byteval!!
in WordDatatypes -> wordval!!
DataType.FLOAT -> floatval!!
else -> throw ArithmeticException("invalid datatype for numeric value: $type")
}
}
fun integerValue(): Int {
return when(type) {
in ByteDatatypes -> byteval!!.toInt()
in WordDatatypes -> wordval!!
DataType.FLOAT -> throw ArithmeticException("float to integer loss of precision")
else -> throw ArithmeticException("invalid datatype for integer value: $type")
}
}
override fun hashCode(): Int {
val bh = byteval?.hashCode() ?: 0x10001234
val wh = wordval?.hashCode() ?: 0x01002345
val fh = floatval?.hashCode() ?: 0x00103456
return bh xor wh xor fh xor heapId.hashCode() xor type.hashCode()
}
override fun equals(other: Any?): Boolean {
if(other==null || other !is RuntimeValue)
return false
if(type==other.type)
return if (type in IterableDatatypes) heapId==other.heapId else compareTo(other)==0
return compareTo(other)==0 // note: datatype doesn't matter
}
operator fun compareTo(other: RuntimeValue): Int {
return if (type in NumericDatatypes && other.type in NumericDatatypes)
numericValue().toDouble().compareTo(other.numericValue().toDouble())
else throw ArithmeticException("comparison can only be done between two numeric values")
}
private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): RuntimeValue {
if(leftDt!=rightDt)
throw ArithmeticException("left and right datatypes are not the same")
if(result.toDouble() < 0 ) {
return when(leftDt) {
DataType.UBYTE, DataType.UWORD -> {
// storing a negative number in an unsigned one is done by storing the 2's complement instead
val number = abs(result.toDouble().toInt())
if(leftDt==DataType.UBYTE)
RuntimeValue(DataType.UBYTE, (number xor 255) + 1)
else
RuntimeValue(DataType.UBYTE, (number xor 65535) + 1)
}
DataType.BYTE -> RuntimeValue(DataType.BYTE, result.toInt())
DataType.WORD -> RuntimeValue(DataType.WORD, result.toInt())
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result)
else -> throw ArithmeticException("$op on non-numeric type")
}
}
return when(leftDt) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, result.toInt() and 255)
DataType.BYTE -> RuntimeValue(DataType.BYTE, result.toInt())
DataType.UWORD -> RuntimeValue(DataType.UWORD, result.toInt() and 65535)
DataType.WORD -> RuntimeValue(DataType.WORD, result.toInt())
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result)
else -> throw ArithmeticException("$op on non-numeric type")
}
}
fun add(other: RuntimeValue): RuntimeValue {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() + v2.toDouble()
return arithResult(type, result, other.type, "add")
}
fun sub(other: RuntimeValue): RuntimeValue {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() - v2.toDouble()
return arithResult(type, result, other.type, "sub")
}
fun mul(other: RuntimeValue): RuntimeValue {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() * v2.toDouble()
return arithResult(type, result, other.type, "mul")
}
fun div(other: RuntimeValue): RuntimeValue {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
if(v2.toDouble()==0.0) {
when (type) {
DataType.UBYTE -> return RuntimeValue(DataType.UBYTE, 255)
DataType.BYTE -> return RuntimeValue(DataType.BYTE, 127)
DataType.UWORD -> return RuntimeValue(DataType.UWORD, 65535)
DataType.WORD -> return RuntimeValue(DataType.WORD, 32767)
else -> {}
}
}
val result = v1.toDouble() / v2.toDouble()
// NOTE: integer division returns integer result!
return when(type) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, result)
DataType.BYTE -> RuntimeValue(DataType.BYTE, result)
DataType.UWORD -> RuntimeValue(DataType.UWORD, result)
DataType.WORD -> RuntimeValue(DataType.WORD, result)
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result)
else -> throw ArithmeticException("div on non-numeric type")
}
}
fun remainder(other: RuntimeValue): RuntimeValue? {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() % v2.toDouble()
return arithResult(type, result, other.type, "remainder")
}
fun pow(other: RuntimeValue): RuntimeValue {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble().pow(v2.toDouble())
return arithResult(type, result, other.type,"pow")
}
fun shl(): RuntimeValue {
val v = integerValue()
return when (type) {
DataType.UBYTE -> return RuntimeValue(type, (v shl 1) and 255)
DataType.BYTE -> {
if(v<0)
RuntimeValue(type, -((-v shl 1) and 255))
else
RuntimeValue(type, (v shl 1) and 255)
}
DataType.UWORD -> return RuntimeValue(type, (v shl 1) and 65535)
DataType.WORD -> {
if(v<0)
RuntimeValue(type, -((-v shl 1) and 65535))
else
RuntimeValue(type, (v shl 1) and 65535)
}
else -> throw ArithmeticException("invalid type for shl: $type")
}
}
fun shr(): RuntimeValue {
val v = integerValue()
return when(type){
DataType.UBYTE -> RuntimeValue(type, (v ushr 1) and 255)
DataType.BYTE -> RuntimeValue(type, v shr 1)
DataType.UWORD -> RuntimeValue(type, (v ushr 1) and 65535)
DataType.WORD -> RuntimeValue(type, v shr 1)
else -> throw ArithmeticException("invalid type for shr: $type")
}
}
fun rol(carry: Boolean): Pair<RuntimeValue, Boolean> {
// 9 or 17 bit rotate left (with carry))
return when(type) {
DataType.UBYTE -> {
val v = byteval!!.toInt()
val newCarry = (v and 0x80) != 0
val newval = (v and 0x7f shl 1) or (if(carry) 1 else 0)
Pair(RuntimeValue(DataType.UBYTE, newval), newCarry)
}
DataType.UWORD -> {
val v = wordval!!
val newCarry = (v and 0x8000) != 0
val newval = (v and 0x7fff shl 1) or (if(carry) 1 else 0)
Pair(RuntimeValue(DataType.UWORD, newval), newCarry)
}
else -> throw ArithmeticException("rol can only work on byte/word")
}
}
fun ror(carry: Boolean): Pair<RuntimeValue, Boolean> {
// 9 or 17 bit rotate right (with carry)
return when(type) {
DataType.UBYTE -> {
val v = byteval!!.toInt()
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x80 else 0)
Pair(RuntimeValue(DataType.UBYTE, newval), newCarry)
}
DataType.UWORD -> {
val v = wordval!!
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x8000 else 0)
Pair(RuntimeValue(DataType.UWORD, newval), newCarry)
}
else -> throw ArithmeticException("ror2 can only work on byte/word")
}
}
fun rol2(): RuntimeValue {
// 8 or 16 bit rotate left
return when(type) {
DataType.UBYTE -> {
val v = byteval!!.toInt()
val carry = (v and 0x80) ushr 7
val newval = (v and 0x7f shl 1) or carry
RuntimeValue(DataType.UBYTE, newval)
}
DataType.UWORD -> {
val v = wordval!!
val carry = (v and 0x8000) ushr 15
val newval = (v and 0x7fff shl 1) or carry
RuntimeValue(DataType.UWORD, newval)
}
else -> throw ArithmeticException("rol2 can only work on byte/word")
}
}
fun ror2(): RuntimeValue {
// 8 or 16 bit rotate right
return when(type) {
DataType.UBYTE -> {
val v = byteval!!.toInt()
val carry = v and 1 shl 7
val newval = (v ushr 1) or carry
RuntimeValue(DataType.UBYTE, newval)
}
DataType.UWORD -> {
val v = wordval!!
val carry = v and 1 shl 15
val newval = (v ushr 1) or carry
RuntimeValue(DataType.UWORD, newval)
}
else -> throw ArithmeticException("ror2 can only work on byte/word")
}
}
fun neg(): RuntimeValue {
return when(type) {
DataType.BYTE -> RuntimeValue(DataType.BYTE, -(byteval!!))
DataType.WORD -> RuntimeValue(DataType.WORD, -(wordval!!))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, -(floatval)!!)
else -> throw ArithmeticException("neg can only work on byte/word/float")
}
}
fun abs(): RuntimeValue {
return when(type) {
DataType.BYTE -> RuntimeValue(DataType.BYTE, abs(byteval!!.toInt()))
DataType.WORD -> RuntimeValue(DataType.WORD, abs(wordval!!))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, abs(floatval!!))
else -> throw ArithmeticException("abs can only work on byte/word/float")
}
}
fun bitand(other: RuntimeValue): RuntimeValue {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 and v2
return RuntimeValue(type, result)
}
fun bitor(other: RuntimeValue): RuntimeValue {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 or v2
return RuntimeValue(type, result)
}
fun bitxor(other: RuntimeValue): RuntimeValue {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 xor v2
return RuntimeValue(type, result)
}
fun and(other: RuntimeValue) = RuntimeValue(DataType.UBYTE, if (this.asBooleanRuntimeValue && other.asBooleanRuntimeValue) 1 else 0)
fun or(other: RuntimeValue) = RuntimeValue(DataType.UBYTE, if (this.asBooleanRuntimeValue || other.asBooleanRuntimeValue) 1 else 0)
fun xor(other: RuntimeValue) = RuntimeValue(DataType.UBYTE, if (this.asBooleanRuntimeValue xor other.asBooleanRuntimeValue) 1 else 0)
fun not() = RuntimeValue(DataType.UBYTE, if (this.asBooleanRuntimeValue) 0 else 1)
fun inv(): RuntimeValue {
return when(type) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, byteval!!.toInt().inv() and 255)
DataType.UWORD -> RuntimeValue(DataType.UWORD, wordval!!.inv() and 65535)
else -> throw ArithmeticException("inv can only work on byte/word")
}
}
fun inc(): RuntimeValue {
return when(type) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, (byteval!! + 1) and 255)
DataType.UWORD -> RuntimeValue(DataType.UWORD, (wordval!! + 1) and 65535)
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, floatval!! + 1)
else -> throw ArithmeticException("inc can only work on byte/word/float")
}
}
fun dec(): RuntimeValue {
return when(type) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, (byteval!! - 1) and 255)
DataType.UWORD -> RuntimeValue(DataType.UWORD, (wordval!! - 1) and 65535)
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, floatval!! - 1)
else -> throw ArithmeticException("dec can only work on byte/word/float")
}
}
fun msb(): RuntimeValue {
return when(type) {
in ByteDatatypes -> RuntimeValue(DataType.UBYTE, 0)
in WordDatatypes -> RuntimeValue(DataType.UBYTE, wordval!! ushr 8 and 255)
else -> throw ArithmeticException("msb can only work on (u)byte/(u)word")
}
}
fun cast(targetType: DataType): RuntimeValue {
return when (type) {
DataType.UBYTE -> {
when (targetType) {
DataType.UBYTE -> this
DataType.BYTE -> {
if(byteval!!<=127)
RuntimeValue(DataType.BYTE, byteval!!)
else
RuntimeValue(DataType.BYTE, -(256-byteval!!))
}
DataType.UWORD -> RuntimeValue(DataType.UWORD, numericValue())
DataType.WORD -> RuntimeValue(DataType.WORD, numericValue())
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.BYTE -> {
when (targetType) {
DataType.BYTE -> this
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue() and 65535)
DataType.WORD -> RuntimeValue(DataType.WORD, integerValue())
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.UWORD -> {
when (targetType) {
in ByteDatatypes -> RuntimeValue(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> this
DataType.WORD -> {
if(integerValue()<=32767)
RuntimeValue(DataType.WORD, integerValue())
else
RuntimeValue(DataType.WORD, -(65536-integerValue()))
}
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.WORD -> {
when (targetType) {
in ByteDatatypes -> RuntimeValue(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue() and 65535)
DataType.WORD -> this
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.FLOAT -> {
when (targetType) {
DataType.BYTE -> {
val integer=numericValue().toInt()
if(integer in -128..127)
RuntimeValue(DataType.BYTE, integer)
else
throw ArithmeticException("overflow when casting float to byte: $this")
}
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, numericValue().toInt() and 255)
DataType.UWORD -> RuntimeValue(DataType.UWORD, numericValue().toInt() and 65535)
DataType.WORD -> {
val integer=numericValue().toInt()
if(integer in -32768..32767)
RuntimeValue(DataType.WORD, integer)
else
throw ArithmeticException("overflow when casting float to word: $this")
}
DataType.FLOAT -> this
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
}

View File

@ -0,0 +1,209 @@
package prog8.astvm
import prog8.compiler.target.c64.Charset
import prog8.compiler.target.c64.Petscii
import java.awt.*
import java.awt.event.KeyEvent
import java.awt.event.KeyListener
import java.awt.image.BufferedImage
import javax.swing.JFrame
import javax.swing.JPanel
import javax.swing.Timer
class BitmapScreenPanel : KeyListener, JPanel() {
private val image = BufferedImage(SCREENWIDTH, SCREENHEIGHT, BufferedImage.TYPE_INT_ARGB)
private val g2d = image.graphics as Graphics2D
private var cursorX: Int=0
private var cursorY: Int=0
init {
val size = Dimension(image.width * SCALING, image.height * SCALING)
minimumSize = size
maximumSize = size
preferredSize = size
clearScreen(6)
isFocusable = true
requestFocusInWindow()
addKeyListener(this)
}
override fun keyTyped(p0: KeyEvent?) {}
override fun keyPressed(p0: KeyEvent?) {
println("pressed: $p0.k")
}
override fun keyReleased(p0: KeyEvent?) {
println("released: $p0")
}
override fun paint(graphics: Graphics?) {
val g2d = graphics as Graphics2D?
g2d!!.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF)
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE)
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)
g2d.drawImage(image, 0, 0, image.width * 3, image.height * 3, null)
}
fun clearScreen(color: Int) {
g2d.background = palette[color and 15]
g2d.clearRect(0, 0, BitmapScreenPanel.SCREENWIDTH, BitmapScreenPanel.SCREENHEIGHT)
cursorX = 0
cursorY = 0
}
fun setPixel(x: Int, y: Int, color: Int) {
image.setRGB(x, y, palette[color and 15].rgb)
}
fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int, color: Int) {
g2d.color = palette[color and 15]
g2d.drawLine(x1, y1, x2, y2)
}
fun printText(text: String, color: Int, lowercase: Boolean) {
val lines = text.split('\n')
for(line in lines.withIndex()) {
printTextSingleLine(line.value, color, lowercase)
if(line.index<lines.size-1) {
cursorX=0
cursorY++
}
}
}
private fun printTextSingleLine(text: String, color: Int, lowercase: Boolean) {
if(color!=1) {
TODO("text can only be white for now")
}
for(clearx in cursorX until cursorX+text.length) {
g2d.clearRect(8*clearx, 8*y, 8, 8)
}
for(sc in Petscii.encodeScreencode(text, lowercase)) {
setChar(cursorX, cursorY, sc)
cursorX++
if(cursorX>=(SCREENWIDTH/8)) {
cursorY++
cursorX=0
}
}
}
fun printChar(char: Short) {
if(char==13.toShort() || char==141.toShort()) {
cursorX=0
cursorY++
} else {
setChar(cursorX, cursorY, char)
cursorX++
if (cursorX >= (SCREENWIDTH / 8)) {
cursorY++
cursorX = 0
}
}
}
fun setChar(x: Int, y: Int, screenCode: Short) {
g2d.clearRect(8*x, 8*y, 8, 8)
g2d.drawImage(Charset.shiftedChars[screenCode.toInt()], 8*x, 8*y , null)
}
fun setCursorPos(x: Int, y: Int) {
cursorX = x
cursorY = y
}
fun getCursorPos(): Pair<Int, Int> {
return Pair(cursorX, cursorY)
}
fun writeText(x: Int, y: Int, text: String, color: Int, lowercase: Boolean) {
var xx=x
if(color!=1) {
TODO("text can only be white for now")
}
for(clearx in xx until xx+text.length) {
g2d.clearRect(8*clearx, 8*y, 8, 8)
}
for(sc in Petscii.encodeScreencode(text, lowercase)) {
setChar(xx++, y, sc)
}
}
companion object {
const val SCREENWIDTH = 320
const val SCREENHEIGHT = 200
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() {
val canvas = BitmapScreenPanel()
init {
val borderWidth = 16
title = "AstVm graphics. Text I/O goes to console."
layout = GridBagLayout()
defaultCloseOperation = JFrame.EXIT_ON_CLOSE
isResizable = false
// 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]
}
var 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)
canvas.requestFocusInWindow()
}
fun start() {
val repaintTimer = Timer(1000 / 60) { repaint() }
repaintTimer.start()
}
}

View File

@ -0,0 +1,39 @@
package prog8.astvm
import prog8.ast.*
import prog8.compiler.HeapValues
class VariablesInitializer(private val runtimeVariables: RuntimeVariables, private val heap: HeapValues) : IAstProcessor {
override fun process(decl: VarDecl): IStatement {
if(decl.type==VarDeclType.VAR) {
val value = when (decl.datatype) {
in NumericDatatypes -> {
if(decl.value !is LiteralValue) {
TODO("evaluate vardecl expression $decl")
//RuntimeValue(decl.datatype, num = evaluate(decl.value!!, program, runtimeVariables, executeSubroutine).numericValue())
} else {
RuntimeValue.from(decl.value as LiteralValue, heap)
}
}
in StringDatatypes -> {
RuntimeValue.from(decl.value as LiteralValue, heap)
}
in ArrayDatatypes -> {
RuntimeValue.from(decl.value as LiteralValue, heap)
}
else -> throw VmExecutionException("weird type ${decl.datatype}")
}
runtimeVariables.define(decl.definingScope(), decl.name, value)
}
return super.process(decl)
}
// override fun process(assignment: Assignment): IStatement {
// if(assignment is VariableInitializationAssignment) {
// println("INIT VAR $assignment")
// }
// return super.process(assignment)
// }
}

View File

@ -37,10 +37,13 @@
; found next one, mark the multiples and return it. ; found next one, mark the multiples and return it.
sieve[candidate_prime] = true sieve[candidate_prime] = true
uword multiple = candidate_prime ; uword multiple = candidate_prime ; TODO the parser should have added a type cast from UBYTE to UWORD here
uword multiple = candidate_prime as uword ; TODO should not be required
while multiple < len(sieve) { while multiple < len(sieve) {
sieve[lsb(multiple)] = true sieve[lsb(multiple)] = true
multiple += candidate_prime multiple += candidate_prime as uword ; TODO cast should not be required
c64scr.print_uw(multiple) ; TODO
c64.CHROUT('\n') ; TODO
} }
return candidate_prime return candidate_prime
} }