diff --git a/.idea/modules.xml b/.idea/modules.xml index 68b9739cd..fd2ee956b 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -14,6 +14,7 @@ + \ No newline at end of file diff --git a/codeGenCpu6502/simulator.iml b/codeGenCpu6502/simulator.iml new file mode 100644 index 000000000..245d3429f --- /dev/null +++ b/codeGenCpu6502/simulator.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/codeGenExperimental6502/build.gradle b/codeGenExperimental6502/build.gradle index b0eb3c62c..9e50c7c9e 100644 --- a/codeGenExperimental6502/build.gradle +++ b/codeGenExperimental6502/build.gradle @@ -26,6 +26,7 @@ compileTestKotlin { dependencies { implementation project(':compilerInterfaces') implementation project(':compilerAst') + implementation project(':simulator') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" // implementation "org.jetbrains.kotlin:kotlin-reflect" implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.14" diff --git a/codeGenExperimental6502/codeGenExperimental6502.iml b/codeGenExperimental6502/codeGenExperimental6502.iml index f04990594..44b4c2662 100644 --- a/codeGenExperimental6502/codeGenExperimental6502.iml +++ b/codeGenExperimental6502/codeGenExperimental6502.iml @@ -12,5 +12,6 @@ + \ No newline at end of file diff --git a/codeGenExperimental6502/src/prog8/codegen/experimental6502/AsmGen.kt b/codeGenExperimental6502/src/prog8/codegen/experimental6502/AsmGen.kt index ef9f83100..4ddd624ce 100644 --- a/codeGenExperimental6502/src/prog8/codegen/experimental6502/AsmGen.kt +++ b/codeGenExperimental6502/src/prog8/codegen/experimental6502/AsmGen.kt @@ -1,8 +1,8 @@ package prog8.codegen.experimental6502 import prog8.ast.Program -import prog8.ast.base.FatalAstException import prog8.compilerinterface.* +import prog8.sim.Simulator class AsmGen(internal val program: Program, internal val errors: IErrorReporter, @@ -14,12 +14,14 @@ class AsmGen(internal val program: Program, println("\n** experimental 65(c)02 code generator **\n") symbolTable.print() + symbolTable.flat.forEach { println(it) } // TODO temporary location to do this: - val intermediateAst = IntermediateAstMaker.transform(program) + val intermediateAst = IntermediateAstMaker(program).transform() intermediateAst.print() - val entry = intermediateAst.entrypoint() ?: throw FatalAstException("no main.start() found") - println(entry) + + val sim = Simulator(intermediateAst, symbolTable) + sim.run() println("..todo: create assembly code into ${options.outputDir.toAbsolutePath()}..") return AssemblyProgram("dummy") diff --git a/codeGenExperimental6502/src/prog8/codegen/experimental6502/IntermediateAstMaker.kt b/codeGenExperimental6502/src/prog8/codegen/experimental6502/IntermediateAstMaker.kt index 8e7f94582..401635838 100644 --- a/codeGenExperimental6502/src/prog8/codegen/experimental6502/IntermediateAstMaker.kt +++ b/codeGenExperimental6502/src/prog8/codegen/experimental6502/IntermediateAstMaker.kt @@ -9,11 +9,10 @@ import prog8.ast.statements.* import prog8.compilerinterface.intermediate.* -object IntermediateAstMaker { - fun transform(srcProgram: Program): PtProgram { +class IntermediateAstMaker(val srcProgram: Program) { + fun transform(): PtProgram { val program = PtProgram( srcProgram.name, - srcProgram.builtinFunctions, srcProgram.memsizer, srcProgram.encoding ) @@ -28,7 +27,6 @@ object IntermediateAstMaker { private fun transform(srcModule: Module): PtModule { val module = PtModule( srcModule.name, - srcModule.source, srcModule.loadAddress, srcModule.isLibrary, srcModule.position @@ -115,8 +113,14 @@ object IntermediateAstMaker { return target } - private fun transform(identifier: IdentifierReference): PtIdentifier = - PtIdentifier(identifier.nameInSource, identifier.position) + private fun transform(identifier: IdentifierReference): PtIdentifier { + val target=identifier.targetStatement(srcProgram)!! as INamedStatement + val targetname = if(target.name in srcProgram.builtinFunctions.names) + listOf("", target.name) + else + target.scopedName + return PtIdentifier(identifier.nameInSource, targetname, identifier.position) + } private fun transform(srcBlock: Block): PtBlock { val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, srcBlock.position) @@ -240,9 +244,10 @@ object IntermediateAstMaker { } private fun transform(srcRepeat: RepeatLoop): PtRepeatLoop { - val repeat = PtRepeatLoop(srcRepeat.iterations==null, srcRepeat.position) - if(srcRepeat.iterations!=null) - repeat.add(transformExpression(srcRepeat.iterations!!)) + if(srcRepeat.iterations==null) + throw FatalAstException("repeat-forever loop should have been replaced with label+jump") + val repeat = PtRepeatLoop(srcRepeat.position) + repeat.add(transformExpression(srcRepeat.iterations!!)) for (statement in srcRepeat.body.statements) { repeat.add(transformStatement(statement)) } diff --git a/compiler/test/ast/TestIntermediateAst.kt b/compiler/test/ast/TestIntermediateAst.kt index 91b645984..7b982506c 100644 --- a/compiler/test/ast/TestIntermediateAst.kt +++ b/compiler/test/ast/TestIntermediateAst.kt @@ -6,6 +6,7 @@ import io.kotest.matchers.ints.shouldBeGreaterThan import io.kotest.matchers.shouldBe import prog8.codegen.experimental6502.IntermediateAstMaker import prog8.codegen.target.C64Target +import prog8.compilerinterface.intermediate.PtVariable import prog8tests.helpers.compileText class TestIntermediateAst: FunSpec({ @@ -24,14 +25,18 @@ class TestIntermediateAst: FunSpec({ } """ val result = compileText(C64Target(), false, text, writeAssembly = false)!! - val ast = IntermediateAstMaker.transform(result.program) + val ast = IntermediateAstMaker(result.program).transform() ast.name shouldBe result.program.name - ast.builtinFunctions.names shouldBe result.program.builtinFunctions.names val entry = ast.entrypoint() ?: fail("no main.start() found") entry.name shouldBe "start" + entry.scopedName shouldBe listOf("main", "start") val blocks = ast.allBlocks().toList() blocks.size shouldBeGreaterThan 1 blocks[0].name shouldBe "main" + blocks[0].scopedName shouldBe listOf("main") + val ccdecl = entry.children[0] as PtVariable + ccdecl.name shouldBe "cc" + ccdecl.scopedName shouldBe listOf("main", "start", "cc") ast.print() } diff --git a/compilerAst/src/prog8/ast/statements/AstStatements.kt b/compilerAst/src/prog8/ast/statements/AstStatements.kt index 13c81a4cc..cd8ac05cc 100644 --- a/compilerAst/src/prog8/ast/statements/AstStatements.kt +++ b/compilerAst/src/prog8/ast/statements/AstStatements.kt @@ -52,7 +52,7 @@ sealed class Statement : Node { // this class is only created as temporary result from looking up the target for a builtin function call. // this node is never actually part of the Ast. -class BuiltinFunctionPlaceholder(val name: String, override val position: Position, override var parent: Node) : Statement() { +class BuiltinFunctionPlaceholder(override val name: String, override val position: Position, override var parent: Node) : Statement(), INamedStatement { override fun linkParents(parent: Node) {} override fun accept(visitor: IAstVisitor) = throw FatalAstException("should not iterate over this node") override fun accept(visitor: AstWalker, parent: Node) = throw FatalAstException("should not iterate over this node") diff --git a/compilerInterfaces/src/prog8/compilerinterface/SymbolTable.kt b/compilerInterfaces/src/prog8/compilerinterface/SymbolTable.kt index 8b5496eb8..aadf3bb9b 100644 --- a/compilerInterfaces/src/prog8/compilerinterface/SymbolTable.kt +++ b/compilerInterfaces/src/prog8/compilerinterface/SymbolTable.kt @@ -23,6 +23,20 @@ class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) { children.values.forEach { flatten(it) } result } + + val allVariables: Collection by lazy { + val vars = mutableListOf() + fun collect(node: StNode) { + for(child in node.children) { + if(child.value.type==StNodeType.STATICVAR) + vars.add(child.value as StStaticVariable) + else + collect(child.value) + } + } + collect(this) + vars + } } diff --git a/compilerInterfaces/src/prog8/compilerinterface/intermediate/AstBase.kt b/compilerInterfaces/src/prog8/compilerinterface/intermediate/AstBase.kt index c36880663..92db71b16 100644 --- a/compilerInterfaces/src/prog8/compilerinterface/intermediate/AstBase.kt +++ b/compilerInterfaces/src/prog8/compilerinterface/intermediate/AstBase.kt @@ -1,15 +1,13 @@ package prog8.compilerinterface.intermediate -import prog8.ast.IBuiltinFunctions import prog8.ast.base.Position import prog8.compilerinterface.IMemSizer import prog8.compilerinterface.IStringEncoding -import prog8.parser.SourceCode // TODO : once the CodeGen doesn't need the old Ast anymore, get rid of the 'Pt' prefixes. -abstract class PtNode(val position: Position, val children: MutableList = mutableListOf()) { +sealed class PtNode(val position: Position, val children: MutableList = mutableListOf()) { lateinit var parent: PtNode @@ -35,14 +33,27 @@ abstract class PtNode(val position: Position, val children: MutableList } -class PtNodeGroup(): PtNode(Position.DUMMY) { +class PtNodeGroup: PtNode(Position.DUMMY) { override fun printProperties() {} } +abstract class PtNamedNode(val name: String, position: Position): PtNode(position) { + val scopedName: List by lazy { + if(this is PtModule) + emptyList() + else { + var namedParent: PtNode = this.parent + while(namedParent !is PtNamedNode) + namedParent = namedParent.parent + namedParent.scopedName + name + } + } +} + + class PtProgram( val name: String, - val builtinFunctions: IBuiltinFunctions, val memsizer: IMemSizer, val encoding: IStringEncoding ) : PtNode(Position.DUMMY) { @@ -60,29 +71,29 @@ class PtProgram( class PtModule( - val name: String, - val source: SourceCode, + name: String, val loadAddress: UInt, val library: Boolean, position: Position -) : PtNode(position) { +) : PtNamedNode(name, position) { override fun printProperties() { print("$name addr=$loadAddress library=$library") } } -class PtBlock(val name: String, +class PtBlock(name: String, val address: UInt?, val library: Boolean, - position: Position) : PtNode(position) { + position: Position +) : PtNamedNode(name, position) { override fun printProperties() { print("$name addr=$address library=$library") } } -class PtDirective(val name: String, position: Position) : PtNode(position) { +class PtDirective(var name: String, position: Position) : PtNode(position) { val args: List get() = children.map { it as PtDirectiveArg } @@ -108,7 +119,7 @@ class PtInlineAssembly(val assembly: String, position: Position) : PtNode(positi } -class PtLabel(val name: String, position: Position) : PtNode(position) { +class PtLabel(name: String, position: Position) : PtNamedNode(name, position) { override fun printProperties() { print(name) } diff --git a/compilerInterfaces/src/prog8/compilerinterface/intermediate/AstExpressions.kt b/compilerInterfaces/src/prog8/compilerinterface/intermediate/AstExpressions.kt index 0fa109b5d..b5e0f188e 100644 --- a/compilerInterfaces/src/prog8/compilerinterface/intermediate/AstExpressions.kt +++ b/compilerInterfaces/src/prog8/compilerinterface/intermediate/AstExpressions.kt @@ -6,8 +6,8 @@ import prog8.compilerinterface.Encoding class PtAddressOf(position: Position) : PtNode(position) { - val addr: PtNode - get() = children.single() + val identifier: PtIdentifier + get() = children.single() as PtIdentifier override fun printProperties() {} } @@ -51,9 +51,9 @@ class PtContainmentCheck(position: Position): PtNode(position) { } -class PtIdentifier(val name: List, position: Position) : PtNode(position) { +class PtIdentifier(val ref: List, val targetName: List, position: Position) : PtNode(position) { override fun printProperties() { - print(name) + print("$ref --> $targetName") } } diff --git a/compilerInterfaces/src/prog8/compilerinterface/intermediate/AstStatements.kt b/compilerInterfaces/src/prog8/compilerinterface/intermediate/AstStatements.kt index 5de8e4b8c..706fb9796 100644 --- a/compilerInterfaces/src/prog8/compilerinterface/intermediate/AstStatements.kt +++ b/compilerInterfaces/src/prog8/compilerinterface/intermediate/AstStatements.kt @@ -11,14 +11,14 @@ import prog8.ast.toHex class PtAsmSub( - val name: String, + name: String, val address: UInt?, val clobbers: Set, val paramRegisters: List, val retvalRegisters: List, val inline: Boolean, position: Position -) : PtNode(position) { +) : PtNamedNode(name, position) { override fun printProperties() { print("$name inline=$inline") } @@ -26,19 +26,21 @@ class PtAsmSub( class PtSub( - val name: String, + name: String, val parameters: List, val returntypes: List, val inline: Boolean, position: Position -) : PtNode(position) { +) : PtNamedNode(name, position) { override fun printProperties() { print(name) } } -class PtAssignment(val augmentable: Boolean, val origin: AssignmentOrigin, position: Position) : PtNode(position) { +class PtAssignment(val augmentable: Boolean, + val origin: AssignmentOrigin, // TODO is this ever used in the codegen? + position: Position) : PtNode(position) { val target: PtAssignTarget get() = children[0] as PtAssignTarget val value: PtNode @@ -156,10 +158,11 @@ class PtPostIncrDecr(val operator: String, position: Position) : PtNode(position } -class PtRepeatLoop(val forever: Boolean, position: Position) : PtNode(position) { - override fun printProperties() { - print("forever=$forever") - } +class PtRepeatLoop(position: Position) : PtNode(position) { + val count: PtNode + get() = children.single() + + override fun printProperties() {} } @@ -177,7 +180,7 @@ class PtReturn(position: Position) : PtNode(position) { } -class PtVariable(val name: String, val type: DataType, position: Position) : PtNode(position) { +class PtVariable(name: String, val type: DataType, position: Position) : PtNamedNode(name, position) { override fun printProperties() { print("$type $name") } @@ -191,7 +194,7 @@ class PtConstant(val name: String, val type: DataType, val value: Double, positi } -class PtMemMapped(val name: String, val type: DataType, val address: UInt, position: Position) : PtNode(position) { +class PtMemMapped(name: String, val type: DataType, val address: UInt, position: Position) : PtNamedNode(name, position) { override fun printProperties() { print("&$type $name = ${address.toHex()}") } diff --git a/examples/test.p8 b/examples/test.p8 index 866ab6561..377a16819 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -12,9 +12,6 @@ main { sub start() { sys.memset(sieve, 256, false) ; clear the sieve, to reset starting situation on subsequent runs - ubyte qq = candidate_prime |> sin8u() |> cos8u() - candidate_prime |> sin8u() |> txt.print_ub() - ; calculate primes txt.print("prime numbers up to 255:\n\n") ubyte amount=0 diff --git a/settings.gradle b/settings.gradle index 6cf8f044d..b5fa4ed1a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,6 +7,7 @@ include( ':codeGenCpu6502', ':codeGenExperimental6502', ':compiler', + ':simulator', ':dbusCompilerService', ':httpCompilerService' ) diff --git a/simulator/build.gradle b/simulator/build.gradle new file mode 100644 index 000000000..235a754f9 --- /dev/null +++ b/simulator/build.gradle @@ -0,0 +1,73 @@ +plugins { + id 'java' + id 'application' + id "org.jetbrains.kotlin.jvm" + id "io.kotest" version "0.3.9" +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(javaVersion) + } +} + +compileKotlin { + kotlinOptions { + jvmTarget = javaVersion + } +} + +compileTestKotlin { + kotlinOptions { + jvmTarget = javaVersion + } +} + +dependencies { + implementation project(':compilerInterfaces') + implementation project(':compilerAst') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + // implementation "org.jetbrains.kotlin:kotlin-reflect" + implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.4' + implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.14" + + testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.1.0' +} + +sourceSets { + main { + java { + srcDirs = ["${project.projectDir}/src"] + } + resources { + srcDirs = ["${project.projectDir}/res"] + } + } + test { + java { + srcDir "${project.projectDir}/test" + } + } +} + +startScripts.enabled = true + +application { + mainClass = 'prog8.SimulatorMainKt' + applicationName = 'p8sim' +} + +test { + // Enable JUnit 5 (Gradle 4.6+). + useJUnitPlatform() + + // Always run tests, even when nothing changed. + dependsOn 'cleanTest' + + // Show test results. + testLogging { + events "skipped", "failed" + } +} + +build.finalizedBy installDist \ No newline at end of file diff --git a/simulator/simulator.iml b/simulator/simulator.iml new file mode 100644 index 000000000..19508275d --- /dev/null +++ b/simulator/simulator.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/simulator/src/prog8/SimulatorMain.kt b/simulator/src/prog8/SimulatorMain.kt new file mode 100644 index 000000000..9c9ce8337 --- /dev/null +++ b/simulator/src/prog8/SimulatorMain.kt @@ -0,0 +1,45 @@ +package prog8 + +import prog8.ast.base.DataType +import prog8.ast.base.Position +import prog8.ast.statements.AssignmentOrigin +import prog8.compilerinterface.Encoding +import prog8.compilerinterface.SymbolTable +import prog8.compilerinterface.intermediate.* +import prog8.sim.MemSizer +import prog8.sim.Simulator +import prog8.sim.StringEncoding + +fun main(args: Array) { + println("\nProg8 simulator by Irmen de Jong (irmen@razorvine.net)") + println("This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html\n") + println("...todo: run from file...") + + val program = PtProgram("test", MemSizer, StringEncoding) + val module = PtModule("test", 0u, false, Position.DUMMY) + val block = PtBlock("main", null, false, Position.DUMMY) + val sub = PtSub("start", emptyList(), emptyList(), false, Position.DUMMY) + block.add(sub) + module.add(block) + program.add(module) + val fcall = PtBuiltinFunctionCall("print", Position.DUMMY).also { + it.add(PtString("Hello, world! From the program.\n", Encoding.DEFAULT, Position.DUMMY)) + } + sub.add(fcall) + val memwrite = PtAssignment(false, AssignmentOrigin.USERCODE, Position.DUMMY).also { assign -> + assign.add(PtAssignTarget(Position.DUMMY).also { tgt -> + tgt.add(PtMemoryByte(Position.DUMMY).also { mb -> + mb.add(PtNumber(DataType.UWORD, 1000.0, Position.DUMMY)) + }) + }) + assign.add(PtNumber(DataType.UBYTE, 99.0, Position.DUMMY)) + } + sub.add(memwrite) + sub.add(PtReturn(Position.DUMMY)) + val symboltable = SymbolTable() + + val sim = Simulator(program, symboltable) + println("memory at $1000=${sim.memory[1000u]}") + sim.run() + println("memory at $1000=${sim.memory[1000u]}") +} diff --git a/simulator/src/prog8/sim/BuiltinFunctions.kt b/simulator/src/prog8/sim/BuiltinFunctions.kt new file mode 100644 index 000000000..f60f0c64b --- /dev/null +++ b/simulator/src/prog8/sim/BuiltinFunctions.kt @@ -0,0 +1,27 @@ +package prog8.sim + +import prog8.ast.base.DataType +import prog8.ast.statements.VarDecl +import prog8.compilerinterface.Encoding +import prog8.compilerinterface.IMemSizer +import prog8.compilerinterface.IStringEncoding + +internal object MemSizer: IMemSizer { + override fun memorySize(dt: DataType): Int { + TODO("Not yet implemented") + } + + override fun memorySize(decl: VarDecl): Int { + TODO("Not yet implemented") + } +} + +internal object StringEncoding: IStringEncoding { + override fun encodeString(str: String, encoding: Encoding): List { + TODO("Not yet implemented") + } + + override fun decodeString(bytes: List, encoding: Encoding): String { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/simulator/src/prog8/sim/Evaluator.kt b/simulator/src/prog8/sim/Evaluator.kt new file mode 100644 index 000000000..acdb53650 --- /dev/null +++ b/simulator/src/prog8/sim/Evaluator.kt @@ -0,0 +1,104 @@ +package prog8.sim + +import prog8.ast.base.DataType +import prog8.compilerinterface.StNodeType +import prog8.compilerinterface.StStaticVariable +import prog8.compilerinterface.SymbolTable +import prog8.compilerinterface.intermediate.* + +class Evaluator( + val symboltable: SymbolTable, + val memory: Memory, + val variables: Variables, + val simulator: Simulator +) { + + fun evaluateExpression(expr: PtNode): Pair { + return when(expr) { + is PtAddressOf -> evaluate(expr) + is PtArrayIndexer -> TODO() + is PtArrayLiteral -> throw IllegalArgumentException("arrayliteral $expr") + is PtBinaryExpression -> TODO() + is PtBuiltinFunctionCall -> TODO() + is PtConstant -> TODO() + is PtContainmentCheck -> TODO() + is PtFunctionCall -> evaluate(expr) + is PtIdentifier -> evaluate(expr) + is PtMemoryByte -> TODO() + is PtNumber -> Pair(expr.number, expr.type) + is PtPipe -> TODO() + is PtPrefix -> TODO() + is PtRange -> throw IllegalArgumentException("range $expr") + is PtString -> throw IllegalArgumentException("string $expr") + is PtTypeCast -> TODO() + else -> TODO("missing evaluator for $expr") + } + } + + internal fun evaluate(fcall: PtFunctionCall): Pair { + val ref = fcall.target.ref + val args = fcall.args.children.map { evaluateExpression(it) } + return when(fcall.target.targetName) { + listOf("sys", "memset") -> { + memory.memset(args[0].first.toUInt(), args[1].first.toUInt(), args[2].first.toInt().toUByte()) + Pair(0.0, DataType.UBYTE) + } + listOf("txt", "print") -> { + print(memory.getString(args.single().first.toUInt())) // strings are passed as a memory address + Pair(0.0, DataType.UBYTE) + } + else -> { + val sub = findPtNode(fcall.target.targetName, fcall) + passCallArgs(sub, args) + return simulator.executeSubroutine(sub) + } + } + } + + private fun passCallArgs(sub: PtSub, args: List>) { + require(sub.parameters.size==args.size) + for ((param, arg) in sub.parameters.zip(args)) { + require(param.type==arg.second) + println("ARG ${param.name} = ${arg.first}") // TODO assign arg + } + } + + 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) + } + StNodeType.CONSTANT -> throw IllegalArgumentException("constants should have been const folded") + else -> throw IllegalArgumentException("weird ref target") + } + } + + 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) + } +} \ No newline at end of file diff --git a/simulator/src/prog8/sim/InstructionPointer.kt b/simulator/src/prog8/sim/InstructionPointer.kt new file mode 100644 index 000000000..64ed3100d --- /dev/null +++ b/simulator/src/prog8/sim/InstructionPointer.kt @@ -0,0 +1,23 @@ +package prog8.sim + +import prog8.compilerinterface.intermediate.PtNode + +class InstructionPointer(var instructions: List, start: Int=0) { + var currentIdx = start + val current: PtNode + get() { + if(currentIdx=32768) + -(65536-word) + else + word + } + + fun setSWord(address: UInt, value: Int) = setWord(address, value.toUInt()) + + fun getWord(address: UInt): UInt { + val lsb = mem[address.toInt()].toUByte() + val msb = mem[address.toInt()+1].toUByte() + return lsb + msb*256u + } + + fun setWord(address: UInt, value: UInt) { + val lsb = value.toByte() + val msb = (value shr 8).toByte() + mem[address.toInt()] = lsb + mem[address.toInt()+1] = msb + } + + fun clear() { + for(i in 0..65535) + mem[i]=0 + } + + fun setString(address: UInt, str: String, zeroTerminate: Boolean=true) { + var addr = address.toInt() + for (it in str.toByteArray(Charsets.ISO_8859_1)) { + mem[addr] = it + addr++ + } + if(zeroTerminate) + mem[addr] = 0 + } + + fun getString(address: UInt): String { + var addr = address.toInt() + while(mem[addr] != 0.toByte()) addr++ + return String(mem, address.toInt(), addr-address.toInt(), Charsets.ISO_8859_1) + } + + fun memset(address: UInt, length: UInt, value: UByte) { + var addr=address.toInt() + val byteval = value.toByte() + repeat(length.toInt()) { + mem[addr] = byteval + addr++ + } + } +} \ No newline at end of file diff --git a/simulator/src/prog8/sim/Simulator.kt b/simulator/src/prog8/sim/Simulator.kt new file mode 100644 index 000000000..61e5d26f9 --- /dev/null +++ b/simulator/src/prog8/sim/Simulator.kt @@ -0,0 +1,124 @@ +package prog8.sim + +import prog8.ast.base.DataType +import prog8.ast.base.Position +import prog8.compilerinterface.Encoding +import prog8.compilerinterface.StStaticVariable +import prog8.compilerinterface.SymbolTable +import prog8.compilerinterface.intermediate.* +import java.util.Stack + + +class ExitProgram(val status: Int): Exception() + + +class Simulator(val program: PtProgram, val symboltable: SymbolTable) { + val memory = Memory() + private val variables = Variables(symboltable, program.memsizer) + 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 (exit: ExitProgram) { + println("Program Exit! Status code: ${exit.status}") + } + } + + internal fun executeSubroutine(sub: PtSub): Pair { + instructionPtr = InstructionPointer(sub.children) + callStack.push(instructionPtr) + while(true) { + val incr = executeStatement(instructionPtr.current) + if(callStack.empty()) + throw ExitProgram(0) + if(incr) + instructionPtr++ + } + } + + internal fun executeStatement(node: PtNode): Boolean { + return when(node) { + is PtAsmSub -> true + is PtAssignment -> execute(node) + is PtBuiltinFunctionCall -> execute(node) + is PtConditionalBranch -> TODO() + is PtDirective -> execute(node) + is PtForLoop -> TODO() + is PtFunctionCall -> { + eval.evaluate(node) // throw away any result + true + } + is PtGosub -> TODO() + is PtIfElse -> TODO() + is PtInlineAssembly -> throw NotImplementedError("can't run inline assembly in simulator at this time") + is PtJump -> TODO() + is PtLabel -> true + is PtPipe -> execute(node) + is PtPostIncrDecr -> TODO() + is PtRepeatLoop -> execute(node) + is PtReturn -> execute(node) + is PtSub -> true // this simulator doesn't "fall through" into nested subroutines + is PtVariable -> true + is PtWhen -> TODO() + else -> TODO("missing code for node $node") + } + } + + private fun execute(repeat: PtRepeatLoop): Boolean { + TODO("repeat $repeat. ${repeat.position}") + return true + } + + private fun execute(pipe: PtPipe): Boolean { + TODO("pipe stmt $pipe") + } + + private fun execute(ret: PtReturn): Boolean { + instructionPtr = callStack.pop() + return true + } + + private fun execute(directive: PtDirective): Boolean { + // TODO handle directive + return true + } + + private fun execute(assign: PtAssignment): Boolean { + val value = eval.evaluateExpression(assign.value) + val identifier = assign.target.identifier + val memoryAddr = assign.target.memory + val array = assign.target.array + if(identifier!=null) { + val targetvar = symboltable.flat.getValue(identifier.targetName) as StStaticVariable + variables.setValue(targetvar, value.first) + } + else if(memoryAddr!=null) { + val address = eval.evaluateExpression(memoryAddr.address) + require(address.second==DataType.UWORD) + require(value.second==DataType.UBYTE) + memory[address.first.toUInt()] = value.first.toInt().toUByte() + } + else if(array!=null) + TODO("assign $value to array $array") + else + 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 + } +} \ No newline at end of file diff --git a/simulator/src/prog8/sim/Variables.kt b/simulator/src/prog8/sim/Variables.kt new file mode 100644 index 000000000..21f6d13fa --- /dev/null +++ b/simulator/src/prog8/sim/Variables.kt @@ -0,0 +1,25 @@ +package prog8.sim + +import prog8.compilerinterface.IMemSizer +import prog8.compilerinterface.StNode +import prog8.compilerinterface.StStaticVariable +import prog8.compilerinterface.SymbolTable + +class Variables(symboltable: SymbolTable, memsizer: IMemSizer) { + + val allVars = symboltable.allVariables + val flatSymbolTable = symboltable.flat + + operator fun get(target: StNode): UInt? { + return null + } + + fun getValue(variable: StStaticVariable): Double { + println("warning: returning dummy value for staticvar ${variable.scopedName}") + return 0.0 + } + + fun setValue(variable: StStaticVariable, value: Double) { + println("warning: discarding value for staticvar ${variable.scopedName}") + } +} \ No newline at end of file diff --git a/simulator/test/TestMemory.kt b/simulator/test/TestMemory.kt new file mode 100644 index 000000000..23a659fc2 --- /dev/null +++ b/simulator/test/TestMemory.kt @@ -0,0 +1,67 @@ +package prog8simulatortests + +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import prog8.sim.Memory + +class TestMemory : FunSpec({ + + val mem = Memory() + + test("unsigned byte") { + mem.clear() + mem[100u] shouldBe 0u + mem[100u] = 99u + mem[100u] shouldBe 99u + mem[100u] = 254u + mem[100u] shouldBe 254u + mem.getSByte(100u) shouldBe -2 + } + + test("signed byte") { + mem.clear() + mem.getSByte(100u) shouldBe 0 + mem.setSByte(100u, 99) + mem.getSByte(100u) shouldBe 99 + mem.setSByte(100u, -2) + mem.getSByte(100u) shouldBe -2 + mem[100u] shouldBe 254u + } + + test("unsigned word") { + mem.clear() + mem.getWord(100u) shouldBe 0u + mem.setWord(100u, 12345u) + mem.getWord(100u) shouldBe 12345u + mem.getSWord(100u) shouldBe 12345 + mem.setWord(100u, 54321u) + mem.getWord(100u) shouldBe 54321u + mem.getSWord(100u) shouldBe -11215 + mem[100u] shouldBe 0x31u + mem[101u] shouldBe 0xd4u + } + + test("signed word") { + mem.clear() + mem.getSWord(100u) shouldBe 0 + mem.setSWord(100u, 12345) + mem.getSWord(100u) shouldBe 12345 + mem.getWord(100u) shouldBe 12345u + mem.setSWord(100u, -12345) + mem.getSWord(100u) shouldBe -12345 + mem.getWord(100u) shouldBe 53191u + mem[100u] shouldBe 0xc7u + mem[101u] shouldBe 0xcfu + } + + test("string") { + mem.clear() + mem[105u] = 1u + mem.setString(100u, "Hello") + mem[100u] shouldBe 'H'.code.toUByte() + mem[104u] shouldBe 'o'.code.toUByte() + mem[105u] shouldBe 0u + + mem.getString(100u) shouldBe "Hello" + } +}) \ No newline at end of file diff --git a/simulator/test/TestSim.kt b/simulator/test/TestSim.kt new file mode 100644 index 000000000..93429d40e --- /dev/null +++ b/simulator/test/TestSim.kt @@ -0,0 +1,46 @@ +package prog8simulatortests + +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import prog8.ast.base.DataType +import prog8.ast.base.Position +import prog8.ast.statements.AssignmentOrigin +import prog8.compilerinterface.Encoding +import prog8.compilerinterface.SymbolTable +import prog8.compilerinterface.intermediate.* +import prog8.sim.MemSizer +import prog8.sim.Simulator +import prog8.sim.StringEncoding + +class TestSim : FunSpec({ + test("simple program simulation") { + val program = PtProgram("test", MemSizer, StringEncoding) + val module = PtModule("test", 0u, false, Position.DUMMY) + val block = PtBlock("main", null, false, Position.DUMMY) + val sub = PtSub("start", emptyList(), emptyList(), false, Position.DUMMY) + block.add(sub) + module.add(block) + program.add(module) + val fcall = PtBuiltinFunctionCall("print", Position.DUMMY).also { + it.add(PtString("Hello, world! From the program.\n", Encoding.DEFAULT, Position.DUMMY)) + } + sub.add(fcall) + val memwrite = PtAssignment(false, AssignmentOrigin.USERCODE, Position.DUMMY).also { assign -> + assign.add(PtAssignTarget(Position.DUMMY).also { tgt -> + tgt.add(PtMemoryByte(Position.DUMMY).also { mb -> + mb.add(PtNumber(DataType.UWORD, 1000.0, Position.DUMMY)) + }) + }) + assign.add(PtNumber(DataType.UBYTE, 99.0, Position.DUMMY)) + } + sub.add(memwrite) + sub.add(PtReturn(Position.DUMMY)) + + val symboltable = SymbolTable() + + val sim = Simulator(program, symboltable) + sim.memory[1000u] shouldBe 0u + sim.run() + sim.memory[1000u] shouldBe 99u + } +}) \ No newline at end of file