diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 27295b4bb..e2df11c69 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -101,8 +101,7 @@ The name of a block must be unique in your entire program. Also be careful when importing other modules; blocks in your own code cannot have the same name as a block defined in an imported module or library. -It's possible to omit this name, but then you can only refer to the contents of the block via its absolute address, -which is required in this case. If you omit *both* name and address, the block is *ignored* by the compiler (and a warning is displayed). +If you omit both the name and address, the entire block is *ignored* by the compiler (and a warning is displayed). This is a way to quickly "comment out" a piece of code that is unfinshed or may contain errors that you want to work on later, because the contents of the ignored block are not fully parsed either. diff --git a/il65/examples/imported2.ill b/il65/examples/imported2.ill index 6b0c70c81..feb94dff6 100644 --- a/il65/examples/imported2.ill +++ b/il65/examples/imported2.ill @@ -15,15 +15,18 @@ const byte snerp2 = snerp+22 X = 42+snerp - return 44+snerp + ;return 44+snerp + return sub foo234234() -> () { A=99+snerp - return A+snerp2 + ;return A+snerp2 + return } sub thingy()->() { - return 99 + ;return 99 + return } } @@ -32,6 +35,7 @@ ; this is imported X = 42 - return 44 + ;return 44 + return } diff --git a/il65/examples/test.ill b/il65/examples/test.ill index a839bbf25..a34bd3950 100644 --- a/il65/examples/test.ill +++ b/il65/examples/test.ill @@ -67,6 +67,12 @@ const byte equal = 4==4 const byte equal2 = (4+hopla)>0 + goto 64738 + + ; A++ + derp++ + cc2-- + goto mega if_eq goto mega @@ -81,6 +87,7 @@ byte equalQQ = 4==4 const byte equalQQ2 = (4+hopla)>0 + sin(1) ; @todo error statement has no effect (returnvalue of builtin function not used) P_carry(1) P_irqd(0) @@ -145,21 +152,24 @@ cool: sub foo () -> () { byte blerp = 3 A=99 - return 33 + ; return 33 + return ultrafoo() X =33 mega: cool: sub ultrafoo() -> () { X= extra233.thingy() - return 33 + ;return 33 + return goto main.mega } } some_label_def: A=44 - return 1+999 + ;return 1+999 + return %breakpoint %asminclude "derp", hopsa %asmbinary "derp", 0, 200 diff --git a/il65/src/il65/Main.kt b/il65/src/il65/Main.kt index 55a251a9a..39effdc90 100644 --- a/il65/src/il65/Main.kt +++ b/il65/src/il65/Main.kt @@ -54,16 +54,16 @@ fun main(args: Array) { StatementReorderer().process(moduleAst) // reorder statements to please the compiler later val globalNamespaceAfterOptimize = moduleAst.definingScope() // create it again, it could have changed in the meantime moduleAst.checkValid(globalNamespaceAfterOptimize, compilerOptions) // check if final tree is valid - moduleAst.checkRecursion() // check if there are recursive subroutine calls + moduleAst.checkRecursion(globalNamespaceAfterOptimize) // check if there are recursive subroutine calls // globalNamespaceAfterOptimize.debugPrint() - // compile the syntax tree into intermediate form, and optimize that + // compile the syntax tree into stackvmProg form, and optimize that val compiler = Compiler(compilerOptions) val intermediate = compiler.compile(moduleAst) intermediate.optimize() -// val assembly = intermediate.compileToAssembly() +// val assembly = stackvmProg.compileToAssembly() // // assembly.assemble(compilerOptions, "input", "output") // val monitorfile = assembly.generateBreakpointList() diff --git a/il65/src/il65/ast/AST.kt b/il65/src/il65/ast/AST.kt index 795202827..77dde1b68 100644 --- a/il65/src/il65/ast/AST.kt +++ b/il65/src/il65/ast/AST.kt @@ -172,6 +172,11 @@ interface IAstProcessor { assignment.value = assignment.value.process(this) return assignment } + + fun process(postIncrDecr: PostIncrDecr): IStatement { + postIncrDecr.target = postIncrDecr.target.process(this) + return postIncrDecr + } } @@ -226,9 +231,9 @@ interface IStatement : Node { interface IFunctionCall { var target: IdentifierReference var arglist: List - var targetStatement: IStatement } + interface INameScope { val name: String val position: Position? @@ -387,7 +392,7 @@ private class GlobalNamespace(override val name: String, is Subroutine -> stmt.scopedname else -> throw NameError("wrong identifier target: $stmt", stmt.position) } - registerUsedName(targetScopedName.joinToString(".")) + registerUsedName(targetScopedName) } return stmt } @@ -406,7 +411,8 @@ class Block(override val name: String, override var statements: MutableList) : IStatement, INameScope { override var position: Position? = null override lateinit var parent: Node - val scopedname: List by lazy { makeScopedName(name) } + val scopedname: String by lazy { makeScopedName(name).joinToString(".") } + override fun linkParents(parent: Node) { this.parent = parent @@ -450,7 +456,7 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?) : No data class Label(val name: String) : IStatement { override var position: Position? = null override lateinit var parent: Node - val scopedname: List by lazy { makeScopedName(name) } + val scopedname: String by lazy { makeScopedName(name).joinToString(".") } override fun linkParents(parent: Node) { this.parent = parent @@ -477,6 +483,10 @@ class Return(var values: List) : IStatement { values = values.map { it.process(processor) } return this } + + override fun toString(): String { + return "Return(values: $values, pos=$position)" + } } @@ -532,7 +542,8 @@ class VarDecl(val type: VarDeclType, override fun process(processor: IAstProcessor) = processor.process(this) - val scopedname: List by lazy { makeScopedName(name) } + val scopedname: String by lazy { makeScopedName(name).joinToString(".") } + fun arraySizeX(namespace: INameScope) : Int? { return arrayspec?.x?.constValue(namespace)?.intvalue @@ -558,6 +569,10 @@ class Assignment(var target: AssignTarget, val aug_op : String?, var value: IExp } override fun process(processor: IAstProcessor) = processor.process(this) + + override fun toString(): String { + return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)") + } } data class AssignTarget(val register: Register?, val identifier: IdentifierReference?) : Node { @@ -700,11 +715,16 @@ data class IdentifierReference(val nameInSource: List) : IExpression { override var position: Position? = null override lateinit var parent: Node + fun targetStatement(namespace: INameScope) = + if(nameInSource.size==1 && BuiltinFunctionNames.contains(nameInSource[0])) + BuiltinFunctionStatementPlaceholder + else + namespace.lookup(nameInSource, this) + override fun linkParents(parent: Node) { this.parent = parent } - override fun constValue(namespace: INameScope): LiteralValue? { val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolException(this) @@ -735,9 +755,10 @@ class PostIncrDecr(var target: AssignTarget, val operator: String) : IStatement target.linkParents(this) } - override fun process(processor: IAstProcessor): IStatement { - target = target.process(processor) - return this + override fun process(processor: IAstProcessor) = processor.process(this) + + override fun toString(): String { + return "PostIncrDecr(op: $operator, target: $target, pos=$position)" } } @@ -745,7 +766,6 @@ class PostIncrDecr(var target: AssignTarget, val operator: String) : IStatement class Jump(val address: Int?, val identifier: IdentifierReference?) : IStatement { override var position: Position? = null override lateinit var parent: Node - var targetStatement: IStatement? = null override fun linkParents(parent: Node) { this.parent = parent @@ -753,13 +773,16 @@ class Jump(val address: Int?, val identifier: IdentifierReference?) : IStatement } override fun process(processor: IAstProcessor) = processor.process(this) + + override fun toString(): String { + return "Jump(addr: $address, identifier: $identifier, target: pos=$position)" + } } class FunctionCall(override var target: IdentifierReference, override var arglist: List) : IExpression, IFunctionCall { override var position: Position? = null override lateinit var parent: Node - override lateinit var targetStatement: IStatement override fun linkParents(parent: Node) { this.parent = parent @@ -825,7 +848,6 @@ class FunctionCall(override var target: IdentifierReference, override var arglis class FunctionCallStatement(override var target: IdentifierReference, override var arglist: List) : IStatement, IFunctionCall { override var position: Position? = null override lateinit var parent: Node - override lateinit var targetStatement: IStatement override fun linkParents(parent: Node) { this.parent = parent @@ -860,7 +882,8 @@ class Subroutine(override val name: String, override var statements: MutableList) : IStatement, INameScope { override var position: Position? = null override lateinit var parent: Node - val scopedname: List by lazy { makeScopedName(name) } + val scopedname: String by lazy { makeScopedName(name).joinToString(".") } + override fun linkParents(parent: Node) { this.parent = parent @@ -930,6 +953,10 @@ class BranchStatement(var condition: BranchCondition, } override fun process(processor: IAstProcessor): IStatement = processor.process(this) + + override fun toString(): String { + return "Branch(cond: $condition, ${statements.size} stmts, ${elsepart.size} else-stmts, pos=$position)" + } } diff --git a/il65/src/il65/ast/AstChecker.kt b/il65/src/il65/ast/AstChecker.kt index a68d4c01c..58ef55666 100644 --- a/il65/src/il65/ast/AstChecker.kt +++ b/il65/src/il65/ast/AstChecker.kt @@ -53,8 +53,10 @@ class AstChecker(private val globalNamespace: INameScope, private val compilerOp override fun process(jump: Jump): IStatement { if(jump.identifier!=null) { val targetStatement = checkFunctionOrLabelExists(jump.identifier, jump) - if(targetStatement!=null) - jump.targetStatement = targetStatement // link to actual jump target + if(targetStatement!=null) { + if(targetStatement is BuiltinFunctionStatementPlaceholder) + checkResult.add(SyntaxError("can't jump to a builtin function", jump.position)) + } } if(jump.address!=null && (jump.address < 0 || jump.address > 65535)) @@ -303,28 +305,38 @@ class AstChecker(private val globalNamespace: INameScope, private val compilerOp ?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCall.position}") val targetStatement = checkFunctionOrLabelExists(functionCall.target, stmtOfExpression) - if(targetStatement!=null) { - functionCall.targetStatement = targetStatement // link to the actual target statement + if(targetStatement!=null) checkBuiltinFunctionCall(functionCall, functionCall.position) - } return super.process(functionCall) } override fun process(functionCall: FunctionCallStatement): IStatement { val targetStatement = checkFunctionOrLabelExists(functionCall.target, functionCall) - if(targetStatement!=null) { - functionCall.targetStatement = targetStatement // link to the actual target statement + if(targetStatement!=null) checkBuiltinFunctionCall(functionCall, functionCall.position) - } return super.process(functionCall) } - private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: IStatement): IStatement? { - if(target.nameInSource.size==1 && BuiltinFunctionNames.contains(target.nameInSource[0])) { - return BuiltinFunctionStatementPlaceholder + override fun process(postIncrDecr: PostIncrDecr): IStatement { + if(postIncrDecr.target.register==null) { + val targetName = postIncrDecr.target.identifier!!.nameInSource + val target = globalNamespace.lookup(targetName, postIncrDecr) + if(target==null) { + checkResult.add(SyntaxError("undefined symbol: ${targetName.joinToString(".")}", postIncrDecr.position)) + } else { + if(target !is VarDecl || target.type==VarDeclType.CONST) { + checkResult.add(SyntaxError("can only increment or decrement a variable", postIncrDecr.position)) + } else if(target.datatype!=DataType.FLOAT && target.datatype!=DataType.WORD && target.datatype!=DataType.BYTE) { + checkResult.add(SyntaxError("can only increment or decrement a byte/float/word variable", postIncrDecr.position)) + } + } } - val targetStatement = globalNamespace.lookup(target.nameInSource, statement) - if(targetStatement is Label || targetStatement is Subroutine) + return super.process(postIncrDecr) + } + + private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: IStatement): IStatement? { + val targetStatement = target.targetStatement(globalNamespace) + if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder) return targetStatement checkResult.add(SyntaxError("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)) return null diff --git a/il65/src/il65/ast/AstIdentifiersChecker.kt b/il65/src/il65/ast/AstIdentifiersChecker.kt index 87146feaf..c59f8a1ad 100644 --- a/il65/src/il65/ast/AstIdentifiersChecker.kt +++ b/il65/src/il65/ast/AstIdentifiersChecker.kt @@ -36,7 +36,7 @@ class AstIdentifiersChecker : IAstProcessor { } override fun process(block: Block): IStatement { - val scopedName = block.scopedname.joinToString(".") + val scopedName = block.scopedname val existing = symbols[scopedName] if(existing!=null) { nameError(block.name, block.position, existing) @@ -51,7 +51,7 @@ class AstIdentifiersChecker : IAstProcessor { // the builtin functions can't be redefined checkResult.add(NameError("builtin function cannot be redefined", decl.position)) - val scopedName = decl.scopedname.joinToString(".") + val scopedName = decl.scopedname val existing = symbols[scopedName] if(existing!=null) { nameError(decl.name, decl.position, existing) @@ -66,7 +66,7 @@ class AstIdentifiersChecker : IAstProcessor { // the builtin functions can't be redefined checkResult.add(NameError("builtin function cannot be redefined", subroutine.position)) } else { - val scopedName = subroutine.scopedname.joinToString(".") + val scopedName = subroutine.scopedname val existing = symbols[scopedName] if (existing != null) { nameError(subroutine.name, subroutine.position, existing) @@ -82,7 +82,7 @@ class AstIdentifiersChecker : IAstProcessor { // the builtin functions can't be redefined checkResult.add(NameError("builtin function cannot be redefined", label.position)) } else { - val scopedName = label.scopedname.joinToString(".") + val scopedName = label.scopedname val existing = symbols[scopedName] if (existing != null) { nameError(label.name, label.position, existing) diff --git a/il65/src/il65/ast/AstRecursionChecker.kt b/il65/src/il65/ast/AstRecursionChecker.kt index 2cc61178f..715cc7788 100644 --- a/il65/src/il65/ast/AstRecursionChecker.kt +++ b/il65/src/il65/ast/AstRecursionChecker.kt @@ -6,8 +6,8 @@ import il65.parser.ParsingFailedError * Checks for the occurrence of recursive subroutine calls */ -fun Module.checkRecursion() { - val checker = AstRecursionChecker() +fun Module.checkRecursion(namespace: INameScope) { + val checker = AstRecursionChecker(namespace) this.process(checker) val checkResult = checker.result() checkResult.forEach { @@ -88,7 +88,7 @@ class DirectedGraph { } -class AstRecursionChecker : IAstProcessor { +class AstRecursionChecker(val namespace: INameScope) : IAstProcessor { private val callGraph = DirectedGraph() fun result(): List { @@ -96,26 +96,32 @@ class AstRecursionChecker : IAstProcessor { if(cycle.isEmpty()) return emptyList() val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" } - return listOf(AstException("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n"+chain)) + return listOf(AstException("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) "+chain)) } override fun process(functionCall: FunctionCallStatement): IStatement { val scope = functionCall.definingScope() - val targetScope = when(functionCall.targetStatement) { - is Subroutine -> functionCall.targetStatement as Subroutine - else -> functionCall.targetStatement.definingScope() + val targetStatement = functionCall.target.targetStatement(namespace) + if(targetStatement!=null) { + val targetScope = when (targetStatement) { + is Subroutine -> targetStatement + else -> targetStatement.definingScope() + } + callGraph.add(scope, targetScope) } - callGraph.add(scope, targetScope) return super.process(functionCall) } override fun process(functionCall: FunctionCall): IExpression { val scope = functionCall.definingScope() - val targetScope = when(functionCall.targetStatement) { - is Subroutine -> functionCall.targetStatement as Subroutine - else -> functionCall.targetStatement.definingScope() + val targetStatement = functionCall.target.targetStatement(namespace) + if(targetStatement!=null) { + val targetScope = when (targetStatement) { + is Subroutine -> targetStatement + else -> targetStatement.definingScope() + } + callGraph.add(scope, targetScope) } - callGraph.add(scope, targetScope) return super.process(functionCall) } } diff --git a/il65/src/il65/compiler/Compiler.kt b/il65/src/il65/compiler/Compiler.kt index b4e2d490c..b34266f9a 100644 --- a/il65/src/il65/compiler/Compiler.kt +++ b/il65/src/il65/compiler/Compiler.kt @@ -85,38 +85,227 @@ data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, va } } +/* + +; source code for a stackvm program +; init memory bytes/words/strings +%memory +0400 01 02 03 04 05 06 07 08 09 22 33 44 55 66 +0500 1111 2222 3333 4444 +1000 "Hello world!\n" +%end_memory +; init global var table with bytes/words/floats/strings +%variables +main.var1 str "This is main.var1" +main.var2 byte aa +main.var3 word ea44 +main.var4 float 3.1415927 +input.prompt str "Enter a number: " +input.result word 0 +%end_variables +; instructions and labels +%instructions + nop + syscall WRITE_MEMSTR word:1000 +loop: + syscall WRITE_VAR str:"input.prompt" + syscall INPUT_VAR str:"input.result" + syscall WRITE_VAR str:"input.result" + push byte:8d + syscall WRITE_CHAR + jump loop +%end_instructions + + + */ class Compiler(private val options: CompilationOptions) { - fun compile(module: Module) : IntermediateForm { - println("\nCompiling parsed source code to intermediate code...") - - // todo + fun compile(module: Module) : StackVmProgram { + println("\nCompiling parsed source code to stackvmProg code...") val namespace = module.definingScope() + + // todo + val intermediate = StackVmProgram(module.name) namespace.debugPrint() - module.statements.filter { it is Block }.map { - with(it as Block) { - "$address $scopedname" + + // create the pool of all variables used in all blocks and scopes + val varGather = VarGatherer(intermediate) + varGather.process(module) + + val stmtGatherer = StatementGatherer(intermediate, namespace) + stmtGatherer.process(module) + + intermediate.toTextLines().forEach { System.out.println(it) } + return intermediate + } + + + class VarGatherer(val stackvmProg: StackVmProgram): IAstProcessor { + // collect all the VarDecls to make them into one global list + override fun process(decl: VarDecl): IStatement { + assert(decl.type==VarDeclType.VAR) {"only VAR decls should remain: CONST and MEMORY should have been processed away"} + stackvmProg.blockvar(decl.scopedname, decl) + return super.process(decl) + } + } + + class StatementGatherer(val stackvmProg: StackVmProgram, val namespace: INameScope): IAstProcessor { + override fun process(subroutine: Subroutine): IStatement { + translate(subroutine.statements) + return super.process(subroutine) + } + + override fun process(block: Block): IStatement { + translate(block.statements) + return super.process(block) + } + + private fun translate(statements: List) { + for (stmt: IStatement in statements) { + when (stmt) { + is AnonymousStatementList -> translate(stmt.statements) + is BuiltinFunctionStatementPlaceholder -> translate(stmt) + is Label -> translate(stmt) + is Return -> translate(stmt) + is Assignment -> translate(stmt) + is PostIncrDecr -> translate(stmt) + is Jump -> translate(stmt) + is FunctionCallStatement -> translate(stmt) + is InlineAssembly -> translate(stmt) + is IfStatement -> translate(stmt) + is BranchStatement -> translate(stmt) + is Directive, is VarDecl, is Subroutine -> {} + else -> TODO("translate statement $stmt") + } } - }.forEach { println(it) } - return IntermediateForm(module.name) + } + + private fun translate(stmt: BranchStatement) { + println("translate: $stmt") + // todo + } + + private fun translate(stmt: IfStatement) { + println("translate: $stmt") + // todo + } + + private fun translate(stmt: InlineAssembly) { + println("translate: $stmt") + TODO("inline assembly not supported yet by stackvm") + } + + private fun translate(stmt: FunctionCallStatement) { + println("translate: $stmt") + // todo + } + + private fun translate(stmt: Jump) { + val instr = + if(stmt.address!=null) { + "jump \$${stmt.address.toString(16)}" + } else { + val target = stmt.identifier!!.targetStatement(namespace)!! + when(target) { + is Label -> "jump ${target.scopedname}" + is Subroutine -> "jump ${target.scopedname}" + else -> throw CompilerException("invalid jump target type ${target::class}") + } + } + stackvmProg.instruction(instr) + } + + private fun translate(stmt: PostIncrDecr) { + if(stmt.target.register!=null) { + TODO("register++/-- not yet implemented") + } else { + val targetStatement = stmt.target.identifier!!.targetStatement(namespace) as VarDecl + when(stmt.operator) { + "++" -> stackvmProg.instruction("inc_var ${targetStatement.scopedname}") + "--" -> stackvmProg.instruction("decr_var ${targetStatement.scopedname}") + } + } + } + + private fun translate(stmt: Assignment) { + println("translate: $stmt") + // todo + } + + private fun translate(stmt: Return) { + if(stmt.values.isNotEmpty()) { + TODO("return with value(s) not yet supported: $stmt") + } + stackvmProg.instruction("return") + } + + private fun translate(stmt: Label) { + stackvmProg.label(stmt.scopedname) + } + + private fun translate(stmt: BuiltinFunctionStatementPlaceholder) { + println("translate: $stmt") + // todo + } } } -class IntermediateForm(val name: String) { + +class StackVmProgram(val name: String) { + + val variables = mutableMapOf() + val instructions = mutableListOf() + fun optimize() { - println("\nOptimizing intermediate code...") + println("\nOptimizing stackvmProg code...") // todo } fun compileToAssembly(): AssemblyResult { - println("\nGenerating assembly code from intermediate code... ") + println("\nGenerating assembly code from stackvmProg code... ") // todo return AssemblyResult(name) } + fun blockvar(scopedname: String, decl: VarDecl) { + println("$scopedname $decl") + variables[scopedname] = decl + } + + fun toTextLines() : List { + val result = mutableListOf("; stackvm program code for: $name") + result.add("%memory") + // todo memory lines for initial memory initialization + result.add("%end_memory") + result.add("%variables") + for(v in variables) { + assert(v.value.type==VarDeclType.VAR || v.value.type==VarDeclType.CONST) + val litval = v.value.value as LiteralValue + val litvalStr = when { + litval.isNumeric -> litval.asNumericValue.toString() + litval.isString -> "\"${litval.strvalue}\"" + else -> TODO() + } + val line = "${v.key} ${v.value.datatype.toString().toLowerCase()} $litvalStr" + result.add(line) + } + result.add("%end_variables") + result.add("%instructions") + result.addAll(instructions) + result.add("%end_nstructions") + return result + } + + fun instruction(s: String) { + instructions.add(" $s") + } + + fun label(name: String) { + instructions.add("$name:") + } } enum class OutputType { diff --git a/il65/src/il65/optimizing/StatementsOptimizer.kt b/il65/src/il65/optimizing/StatementsOptimizer.kt index df109dedc..40cd24076 100644 --- a/il65/src/il65/optimizing/StatementsOptimizer.kt +++ b/il65/src/il65/optimizing/StatementsOptimizer.kt @@ -83,7 +83,7 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso is Subroutine -> stmt.scopedname else -> throw AstException("invalid call target node type: ${stmt::class}") } - globalNamespace.registerUsedName(scopedName.joinToString(".")) + globalNamespace.registerUsedName(scopedName) } fun removeUnusedNodes(usedNames: Set, allScopedSymbolDefinitions: MutableMap) {