building more of the simulator

This commit is contained in:
Irmen de Jong 2022-03-08 20:18:19 +01:00
parent abcdd331db
commit e6e84859b7
5 changed files with 245 additions and 80 deletions

View File

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

View File

@ -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<Double, DataType> {
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<Double, DataType> {
val address = evaluateExpression(memoryByte.address).first.toUInt()
val value = memory[address]
return Pair(value.toDouble(), DataType.UBYTE)
}
private fun evaluate(arrayIdx: PtArrayIndexer): Pair<Double, DataType> {
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<Double, DataType> {
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<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)
}
">=" -> {
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<Double, DataType> {
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<Double, DataType> {
println("TODO: builtin function call ${fcall.name}")
return Pair(0.0, DataType.UBYTE)
}
private fun passCallArgs(sub: PtSub, args: List<Pair<Double, DataType>>) {
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<String>, 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<PtNamedNode>()
sub = nodes.first { it.name==namepart }
}
return sub as PtSub
}
private fun evaluate(ident: PtIdentifier): Pair<Double, DataType> {
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<Double, DataType> {
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)
}
}

View File

@ -16,8 +16,7 @@ class InstructionPointer(var instructions: List<PtNode>, start: Int=0) {
require(instructions.isNotEmpty())
}
operator fun inc(): InstructionPointer {
fun next() {
currentIdx++
return this
}
}

View File

@ -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<Double, DataType>?): FlowcontrolException()
class ExitProgram(val status: Int): FlowcontrolException()
class JumpTo(val targetName: List<String>): 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<InstructionPointer>()
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<Double, DataType> {
instructionPtr = InstructionPointer(sub.children)
callStack.push(instructionPtr)
internal fun executeSubroutine(sub: PtSub): Pair<Double, DataType>? {
return try {
executeStatementList(sub.children)
null
} catch(r: ReturnValue) {
r.value
}
}
internal fun executeStatementList(nodes: List<PtNode>) {
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<String>, scope: PtNode): PtNode {
var root = scope
while(root !is PtProgram) {
root=root.parent
}
val block = root.allBlocks().first {
it.name == scopedName.first()
}
var node: PtNode = block
scopedName.drop(1).forEach { namepart->
val nodes = node.children.filterIsInstance<PtNamedNode>()
node = nodes.first { it.name==namepart }
}
return node
}

View File

@ -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<String, Encoding>?,
val array: DoubleArray?
)
val allVars = symboltable.allVariables
val flatSymbolTable = symboltable.flat
val allocations = mutableMapOf<StStaticVariable, UInt>()
val values = mutableMapOf<StStaticVariable, Value>()
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
}
}