diff --git a/simulator/src/prog8/sim/BuiltinFunctions.kt b/simulator/src/prog8/sim/BuiltinFunctions.kt index f60f0c64b..621eb35a0 100644 --- a/simulator/src/prog8/sim/BuiltinFunctions.kt +++ b/simulator/src/prog8/sim/BuiltinFunctions.kt @@ -1,6 +1,7 @@ package prog8.sim -import prog8.ast.base.DataType +import prog8.ast.base.* +import prog8.ast.expressions.StringLiteral import prog8.ast.statements.VarDecl import prog8.compilerinterface.Encoding import prog8.compilerinterface.IMemSizer @@ -8,11 +9,26 @@ import prog8.compilerinterface.IStringEncoding internal object MemSizer: IMemSizer { override fun memorySize(dt: DataType): Int { - TODO("Not yet implemented") + return when(dt) { + in ByteDatatypes -> 1 + in WordDatatypes, in PassByReferenceDatatypes -> 2 + DataType.FLOAT -> Double.SIZE_BYTES + else -> Int.MIN_VALUE + } } override fun memorySize(decl: VarDecl): Int { - TODO("Not yet implemented") + return when(decl.type) { + VarDeclType.CONST -> 0 + VarDeclType.VAR, VarDeclType.MEMORY -> { + when(val dt = decl.datatype) { + in NumericDatatypes -> return memorySize(dt) + in ArrayDatatypes -> decl.arraysize!!.constIndex()!! * memorySize(ArrayToElementTypes.getValue(dt)) + DataType.STR -> (decl.value as StringLiteral).value.length + 1 + else -> 0 + } + } + } } } diff --git a/simulator/src/prog8/sim/Evaluator.kt b/simulator/src/prog8/sim/Evaluator.kt index acdb53650..17dde8c0c 100644 --- a/simulator/src/prog8/sim/Evaluator.kt +++ b/simulator/src/prog8/sim/Evaluator.kt @@ -1,6 +1,7 @@ package prog8.sim import prog8.ast.base.DataType +import prog8.compilerinterface.Encoding import prog8.compilerinterface.StNodeType import prog8.compilerinterface.StStaticVariable import prog8.compilerinterface.SymbolTable @@ -16,15 +17,15 @@ class Evaluator( fun evaluateExpression(expr: PtNode): Pair { return when(expr) { is PtAddressOf -> evaluate(expr) - is PtArrayIndexer -> TODO() + is PtArrayIndexer -> evaluate(expr) is PtArrayLiteral -> throw IllegalArgumentException("arrayliteral $expr") - is PtBinaryExpression -> TODO() - is PtBuiltinFunctionCall -> TODO() - is PtConstant -> TODO() + is PtBinaryExpression -> evaluate(expr) + is PtBuiltinFunctionCall -> evaluate(expr) + is PtConstant -> Pair(expr.value, expr.type) is PtContainmentCheck -> TODO() is PtFunctionCall -> evaluate(expr) is PtIdentifier -> evaluate(expr) - is PtMemoryByte -> TODO() + is PtMemoryByte -> evaluate(expr) is PtNumber -> Pair(expr.number, expr.type) is PtPipe -> TODO() is PtPrefix -> TODO() @@ -35,6 +36,59 @@ class Evaluator( } } + private fun evaluate(memoryByte: PtMemoryByte): Pair { + val address = evaluateExpression(memoryByte.address).first.toUInt() + val value = memory[address] + return Pair(value.toDouble(), DataType.UBYTE) + } + + private fun evaluate(arrayIdx: PtArrayIndexer): Pair { + val index = evaluateExpression(arrayIdx.index) + println("TODO: get array value ${arrayIdx.variable.ref}[${index.first.toInt()}]") + return Pair(0.0, DataType.UBYTE) + } + + internal fun evaluate(expr: PtBinaryExpression): Pair { + val left = evaluateExpression(expr.left) + val right = evaluateExpression(expr.right) + require(left.second==right.second) + + // TODO implement 8/16 bit maths + + return when(expr.operator) { + "+" -> Pair(left.first+right.first, left.second) + "-" -> Pair(left.first-right.first, left.second) + "*" -> Pair(left.first*right.first, left.second) + "/" -> Pair(left.first/right.first, left.second) + "==" -> { + val bool = if(left.first==right.first) 1 else 0 + Pair(bool.toDouble(), DataType.UBYTE) + } + "!=" -> { + val bool = if(left.first!=right.first) 1 else 0 + Pair(bool.toDouble(), DataType.UBYTE) + } + "<" -> { + val bool = if(left.first" -> { + val bool = if(left.first>right.first) 1 else 0 + Pair(bool.toDouble(), DataType.UBYTE) + } + "<=" -> { + val bool = if(left.first<=right.first) 1 else 0 + Pair(bool.toDouble(), DataType.UBYTE) + } + ">=" -> { + val bool = if(left.first>=right.first) 1 else 0 + Pair(bool.toDouble(), DataType.UBYTE) + } + + else -> TODO("binexpr operator ${expr.operator}") + } + } + internal fun evaluate(fcall: PtFunctionCall): Pair { val ref = fcall.target.ref val args = fcall.args.children.map { evaluateExpression(it) } @@ -47,14 +101,26 @@ class Evaluator( print(memory.getString(args.single().first.toUInt())) // strings are passed as a memory address Pair(0.0, DataType.UBYTE) } + listOf("txt", "print_uw") -> { + print(args.single().first.toUInt()) + Pair(0.0, DataType.UBYTE) + } else -> { - val sub = findPtNode(fcall.target.targetName, fcall) - passCallArgs(sub, args) - return simulator.executeSubroutine(sub) + val node = findPtNode(fcall.target.targetName, fcall) + if(node is PtAsmSub) + throw NotImplementedError("simulator can't run asmsub ${node.name}") + node as PtSub + passCallArgs(node, args) + return simulator.executeSubroutine(node)!! } } } + internal fun evaluate(fcall: PtBuiltinFunctionCall): Pair { + println("TODO: builtin function call ${fcall.name}") + return Pair(0.0, DataType.UBYTE) + } + private fun passCallArgs(sub: PtSub, args: List>) { require(sub.parameters.size==args.size) for ((param, arg) in sub.parameters.zip(args)) { @@ -63,28 +129,15 @@ class Evaluator( } } - private fun findPtNode(scopedName: List, scope: PtNode): PtSub { - var root = scope - while(root !is PtProgram) { - root=root.parent - } - val block = root.allBlocks().first { - it.name == scopedName.first() - } - var sub: PtNode = block - scopedName.drop(1).forEach { namepart-> - val nodes = sub.children.filterIsInstance() - sub = nodes.first { it.name==namepart } - } - return sub as PtSub - } - private fun evaluate(ident: PtIdentifier): Pair { val target = symboltable.flat.getValue(ident.targetName) when(target.type) { StNodeType.STATICVAR -> { val variable = target as StStaticVariable - return Pair(variables.getValue(variable), target.dt) + val value = variables.getValue(variable) + if(value.number==null) + TODO("${ident.ref} -> $value") + return Pair(value.number!!, target.dt) } StNodeType.CONSTANT -> throw IllegalArgumentException("constants should have been const folded") else -> throw IllegalArgumentException("weird ref target") @@ -92,13 +145,7 @@ class Evaluator( } private fun evaluate(addressOf: PtAddressOf): Pair { - val target = symboltable.flat.getValue(addressOf.identifier.targetName) - val alloc = variables[target] - return if(alloc==null) { - // TODO throw IllegalArgumentException("can't get address of ${target.scopedName}") - println("warning: returning dummy address for ${target.scopedName}") - Pair(4096.0, DataType.UWORD) - } else - Pair(alloc.toDouble(), DataType.UWORD) + val target = symboltable.flat.getValue(addressOf.identifier.targetName) as StStaticVariable + return Pair(variables.getAddress(target).toDouble(), DataType.UWORD) } } \ No newline at end of file diff --git a/simulator/src/prog8/sim/InstructionPointer.kt b/simulator/src/prog8/sim/InstructionPointer.kt index 64ed3100d..3bcb7e8a5 100644 --- a/simulator/src/prog8/sim/InstructionPointer.kt +++ b/simulator/src/prog8/sim/InstructionPointer.kt @@ -16,8 +16,7 @@ class InstructionPointer(var instructions: List, start: Int=0) { require(instructions.isNotEmpty()) } - operator fun inc(): InstructionPointer { + fun next() { currentIdx++ - return this } } \ No newline at end of file diff --git a/simulator/src/prog8/sim/Simulator.kt b/simulator/src/prog8/sim/Simulator.kt index 61e5d26f9..dd1384250 100644 --- a/simulator/src/prog8/sim/Simulator.kt +++ b/simulator/src/prog8/sim/Simulator.kt @@ -9,43 +9,63 @@ import prog8.compilerinterface.intermediate.* import java.util.Stack -class ExitProgram(val status: Int): Exception() +sealed class FlowcontrolException : Exception() + +class ReturnValue(val value: Pair?): FlowcontrolException() + +class ExitProgram(val status: Int): FlowcontrolException() + +class JumpTo(val targetName: List): FlowcontrolException() class Simulator(val program: PtProgram, val symboltable: SymbolTable) { val memory = Memory() - private val variables = Variables(symboltable, program.memsizer) + private val variables = Variables(symboltable) private val eval = Evaluator(symboltable, memory, variables, this) - private val callStack = Stack() - private var instructionPtr = InstructionPointer(listOf(PtReturn(Position.DUMMY))) fun run() { memory.clear() val start = program.entrypoint() ?: throw NoSuchElementException("no main.start() found") try { executeSubroutine(start) + } catch(r: ReturnValue) { + println("Program Exit.") } catch (exit: ExitProgram) { println("Program Exit! Status code: ${exit.status}") } } - internal fun executeSubroutine(sub: PtSub): Pair { - instructionPtr = InstructionPointer(sub.children) - callStack.push(instructionPtr) + internal fun executeSubroutine(sub: PtSub): Pair? { + return try { + executeStatementList(sub.children) + null + } catch(r: ReturnValue) { + r.value + } + } + + internal fun executeStatementList(nodes: List) { + var instructionPtr = InstructionPointer(nodes) while(true) { - val incr = executeStatement(instructionPtr.current) - if(callStack.empty()) - throw ExitProgram(0) - if(incr) - instructionPtr++ + try { + if (executeStatement(instructionPtr.current)) + instructionPtr.next() + } catch(jump: JumpTo) { + val target = findPtNode(jump.targetName, instructionPtr.current) + val nodes = target.parent.children + instructionPtr = InstructionPointer(nodes, nodes.indexOf(target)) + } } } internal fun executeStatement(node: PtNode): Boolean { return when(node) { - is PtAsmSub -> true + is PtAsmSub -> throw NotImplementedError("can't run assembly subroutine in simulator at this time") is PtAssignment -> execute(node) - is PtBuiltinFunctionCall -> execute(node) + is PtBuiltinFunctionCall -> { + eval.evaluate(node) // throw away any result + true + } is PtConditionalBranch -> TODO() is PtDirective -> execute(node) is PtForLoop -> TODO() @@ -54,12 +74,12 @@ class Simulator(val program: PtProgram, val symboltable: SymbolTable) { true } is PtGosub -> TODO() - is PtIfElse -> TODO() + is PtIfElse -> execute(node) is PtInlineAssembly -> throw NotImplementedError("can't run inline assembly in simulator at this time") - is PtJump -> TODO() + is PtJump -> execute(node) is PtLabel -> true is PtPipe -> execute(node) - is PtPostIncrDecr -> TODO() + is PtPostIncrDecr -> execute(node) is PtRepeatLoop -> execute(node) is PtReturn -> execute(node) is PtSub -> true // this simulator doesn't "fall through" into nested subroutines @@ -69,6 +89,47 @@ class Simulator(val program: PtProgram, val symboltable: SymbolTable) { } } + private fun execute(jump: PtJump): Boolean { + if(jump.address!=null) + throw NotImplementedError("simulator can't jump into memory machine code") + else if(jump.generatedLabel!=null) + throw NotImplementedError("simulator can't jump into generated label") + else { + throw JumpTo(jump.identifier!!.targetName) + } + } + + private fun execute(post: PtPostIncrDecr): Boolean { + val identifier = post.target.identifier + val memoryAddr = post.target.memory + val array = post.target.array + if(identifier!=null) { + val variable = symboltable.lookup(identifier.targetName) as StStaticVariable + var value = variables.getValue(variable).number!! + if(post.operator=="++") value++ else value-- + variables.setValue(variable, Variables.Value(value, null, null)) + } else if(memoryAddr!=null) { + val addr = eval.evaluateExpression(memoryAddr.address).first.toUInt() + if(post.operator=="++") + memory[addr] = (memory[addr]+1u).toUByte() + else + memory[addr] = (memory[addr]-1u).toUByte() + } else if(array!=null) { + println("TODO: ${array.variable.ref}[] ${post.operator}") + } + return true + } + + private fun execute(ifelse: PtIfElse): Boolean { + val condition = eval.evaluateExpression(ifelse.condition) + if(condition.first!=0.0) { + println("TODO: if part") + } else { + println("TODO: else part") + } + return true + } + private fun execute(repeat: PtRepeatLoop): Boolean { TODO("repeat $repeat. ${repeat.position}") return true @@ -79,8 +140,10 @@ class Simulator(val program: PtProgram, val symboltable: SymbolTable) { } private fun execute(ret: PtReturn): Boolean { - instructionPtr = callStack.pop() - return true + if(ret.hasValue) + throw ReturnValue(eval.evaluateExpression(ret.value!!)) + else + throw ReturnValue(null) } private fun execute(directive: PtDirective): Boolean { @@ -95,7 +158,7 @@ class Simulator(val program: PtProgram, val symboltable: SymbolTable) { val array = assign.target.array if(identifier!=null) { val targetvar = symboltable.flat.getValue(identifier.targetName) as StStaticVariable - variables.setValue(targetvar, value.first) + variables.setValue(targetvar, Variables.Value(value.first, null, null)) } else if(memoryAddr!=null) { val address = eval.evaluateExpression(memoryAddr.address) @@ -109,16 +172,22 @@ class Simulator(val program: PtProgram, val symboltable: SymbolTable) { throw IllegalArgumentException("missing assign target") return true } +} - private fun execute(fcall: PtBuiltinFunctionCall): Boolean { - when(fcall.name) { - "print" -> { - val string = fcall.children.single() as PtString - require(string.encoding==Encoding.DEFAULT) - print(string.value) - } - else -> TODO("missing builtin function ${fcall.name}") - } - return true + + +internal fun findPtNode(scopedName: List, scope: PtNode): PtNode { + var root = scope + while(root !is PtProgram) { + root=root.parent } -} \ No newline at end of file + val block = root.allBlocks().first { + it.name == scopedName.first() + } + var node: PtNode = block + scopedName.drop(1).forEach { namepart-> + val nodes = node.children.filterIsInstance() + node = nodes.first { it.name==namepart } + } + return node +} diff --git a/simulator/src/prog8/sim/Variables.kt b/simulator/src/prog8/sim/Variables.kt index 21f6d13fa..29ddcfd9a 100644 --- a/simulator/src/prog8/sim/Variables.kt +++ b/simulator/src/prog8/sim/Variables.kt @@ -1,25 +1,59 @@ package prog8.sim -import prog8.compilerinterface.IMemSizer -import prog8.compilerinterface.StNode +import prog8.ast.base.ArrayDatatypes +import prog8.ast.base.DataType +import prog8.ast.base.FatalAstException +import prog8.ast.base.NumericDatatypes +import prog8.compilerinterface.Encoding import prog8.compilerinterface.StStaticVariable import prog8.compilerinterface.SymbolTable -class Variables(symboltable: SymbolTable, memsizer: IMemSizer) { +class Variables(symboltable: SymbolTable) { + + class Value( + val number: Double?, + val string: Pair?, + val array: DoubleArray? + ) val allVars = symboltable.allVariables val flatSymbolTable = symboltable.flat + val allocations = mutableMapOf() + val values = mutableMapOf() - operator fun get(target: StNode): UInt? { - return null + init { + var address = 0x1000u + for (variable in allVars) { + allocations[variable] = address + address += MemSizer.memorySize(variable.dt).toUInt() + when(variable.dt) { + in NumericDatatypes -> { + val number = if(variable.initialNumericValue==null) 0.0 else variable.initialNumericValue!! + values[variable] = Value(number, null, null) + } + DataType.STR -> { + values[variable] = Value(null, variable.initialStringValue!!, null) + } + in ArrayDatatypes -> { + val array = if(variable.initialArrayValue==null) DoubleArray(variable.arraysize!!) else variable.initialArrayValue!! + values[variable] = Value(null, null, array) + } + else -> throw FatalAstException("weird dt") + } + } } - fun getValue(variable: StStaticVariable): Double { - println("warning: returning dummy value for staticvar ${variable.scopedName}") - return 0.0 - } + fun getAddress(variable: StStaticVariable): UInt = allocations.getValue(variable) - fun setValue(variable: StStaticVariable, value: Double) { - println("warning: discarding value for staticvar ${variable.scopedName}") + fun getValue(variable: StStaticVariable): Value = values.getValue(variable) + + fun setValue(variable: StStaticVariable, value: Value) { + when(variable.dt) { + in NumericDatatypes-> require(value.number!=null) + DataType.STR -> require(value.string!=null) + in ArrayDatatypes -> require(value.array!=null) + else -> throw FatalAstException("weird dt") + } + values[variable] = value } } \ No newline at end of file