From e53c860f1a86f9d87da24056661711234271b9c1 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 23 Jun 2019 22:08:48 +0200 Subject: [PATCH] first go at ast-based virtual machine (rather than the stackvm that uses intermediate code) --- .idea/uiDesigner.xml | 124 +++++ compiler/src/prog8/CompilerMain.kt | 14 +- compiler/src/prog8/ast/AST.kt | 2 +- compiler/src/prog8/astvm/AstVm.kt | 319 +++++++++++ compiler/src/prog8/astvm/CallStack.kt | 18 + compiler/src/prog8/astvm/Expressions.kt | 102 ++++ compiler/src/prog8/astvm/Memory.kt | 102 ++++ compiler/src/prog8/astvm/RuntimeValue.kt | 516 ++++++++++++++++++ compiler/src/prog8/astvm/ScreenDialog.kt | 209 +++++++ .../src/prog8/astvm/VariablesInitializer.kt | 39 ++ examples/primes.p8 | 7 +- 11 files changed, 1448 insertions(+), 4 deletions(-) create mode 100644 .idea/uiDesigner.xml create mode 100644 compiler/src/prog8/astvm/AstVm.kt create mode 100644 compiler/src/prog8/astvm/CallStack.kt create mode 100644 compiler/src/prog8/astvm/Expressions.kt create mode 100644 compiler/src/prog8/astvm/Memory.kt create mode 100644 compiler/src/prog8/astvm/RuntimeValue.kt create mode 100644 compiler/src/prog8/astvm/ScreenDialog.kt create mode 100644 compiler/src/prog8/astvm/VariablesInitializer.kt diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 000000000..e96534fb2 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index 5f4e9944f..8d2286424 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -1,6 +1,7 @@ package prog8 import prog8.ast.* +import prog8.astvm.AstVm import prog8.compiler.* import prog8.compiler.target.c64.AsmGen import prog8.compiler.target.c64.C64Zeropage @@ -49,6 +50,7 @@ private fun compileMain(args: Array) { var writeAssembly = true var optimize = true var optimizeInlining = true + var launchAstVm = false for (arg in args) { if(arg=="-emu") emulatorToStart = "x64" @@ -62,6 +64,8 @@ private fun compileMain(args: Array) { optimize = false else if(arg=="-nooptinline") optimizeInlining = false + else if(arg=="-avm") + launchAstVm = true else if(!arg.startsWith("-")) moduleFile = arg else @@ -72,12 +76,13 @@ private fun compileMain(args: Array) { val filepath = Paths.get(moduleFile).normalize() var programname = "?" + lateinit var programAst: Program try { val totalTime = measureTimeMillis { // import main module and everything it needs println("Parsing...") - val programAst = Program(moduleName(filepath.fileName), mutableListOf()) + programAst = Program(moduleName(filepath.fileName), mutableListOf()) importModule(programAst, filepath) val compilerOptions = determineCompilationOptions(programAst) @@ -180,6 +185,12 @@ private fun compileMain(args: Array) { throw x } + if(launchAstVm) { + println("\nLaunching AST-based vm...") + val vm = AstVm(programAst) + vm.run() + } + if(emulatorToStart.isNotEmpty()) { println("\nStarting C-64 emulator $emulatorToStart...") 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(" [-noasm] don't create assembly code") 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(" [-nooptinline] don't perform subroutine inlining optimizations") System.err.println(" modulefile main module file to compile") diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index a723a3e08..9e57291ec 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -1597,7 +1597,7 @@ data class IdentifierReference(val nameInSource: List, override val posi fun heapId(namespace: INameScope): Int { 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") } } diff --git a/compiler/src/prog8/astvm/AstVm.kt b/compiler/src/prog8/astvm/AstVm.kt new file mode 100644 index 000000000..ef4a63942 --- /dev/null +++ b/compiler/src/prog8/astvm/AstVm.kt @@ -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>().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): List { + 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): List = args.map { evaluate(it, program, runtimeVariables, ::executeSubroutine) } + + private fun performBuiltinFunction(name: String, args: List) { + 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) { + 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 + } + } + } + } +} diff --git a/compiler/src/prog8/astvm/CallStack.kt b/compiler/src/prog8/astvm/CallStack.kt new file mode 100644 index 000000000..195e714e1 --- /dev/null +++ b/compiler/src/prog8/astvm/CallStack.kt @@ -0,0 +1,18 @@ +package prog8.astvm + +import prog8.ast.INameScope +import java.util.* + +class CallStack { + + private val stack = Stack>() + + fun pop(): Pair { + return stack.pop() + } + + fun push(scope: INameScope, index: Int) { + stack.push(Pair(scope, index)) + } + +} diff --git a/compiler/src/prog8/astvm/Expressions.kt b/compiler/src/prog8/astvm/Expressions.kt new file mode 100644 index 000000000..58a52591e --- /dev/null +++ b/compiler/src/prog8/astvm/Expressions.kt @@ -0,0 +1,102 @@ +package prog8.astvm + +import prog8.ast.* + +fun evaluate(expr: IExpression, program: Program, runtimeVars: RuntimeVariables, + executeSubroutine: (sub: Subroutine, args: List) -> List): 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") + } + } +} diff --git a/compiler/src/prog8/astvm/Memory.kt b/compiler/src/prog8/astvm/Memory.kt new file mode 100644 index 000000000..b6050b315 --- /dev/null +++ b/compiler/src/prog8/astvm/Memory.kt @@ -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() + 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] + } +} diff --git a/compiler/src/prog8/astvm/RuntimeValue.kt b/compiler/src/prog8/astvm/RuntimeValue.kt new file mode 100644 index 000000000..165c9b3d1 --- /dev/null +++ b/compiler/src/prog8/astvm/RuntimeValue.kt @@ -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?=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 { + // 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 { + // 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") + } + } + +} diff --git a/compiler/src/prog8/astvm/ScreenDialog.kt b/compiler/src/prog8/astvm/ScreenDialog.kt new file mode 100644 index 000000000..e84140f7a --- /dev/null +++ b/compiler/src/prog8/astvm/ScreenDialog.kt @@ -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=(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 { + 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() + } +} diff --git a/compiler/src/prog8/astvm/VariablesInitializer.kt b/compiler/src/prog8/astvm/VariablesInitializer.kt new file mode 100644 index 000000000..34b27d317 --- /dev/null +++ b/compiler/src/prog8/astvm/VariablesInitializer.kt @@ -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) +// } + +} diff --git a/examples/primes.p8 b/examples/primes.p8 index f89672ed3..62a106495 100644 --- a/examples/primes.p8 +++ b/examples/primes.p8 @@ -37,10 +37,13 @@ ; found next one, mark the multiples and return it. 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) { 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 }