diff --git a/compiler/src/prog8/ast/StmtReorderer.kt b/compiler/src/prog8/ast/StmtReorderer.kt index 673843efd..1f867e483 100644 --- a/compiler/src/prog8/ast/StmtReorderer.kt +++ b/compiler/src/prog8/ast/StmtReorderer.kt @@ -1,5 +1,7 @@ package prog8.ast +import prog8.functions.BuiltinFunctions + internal fun Program.reorderStatements() { val initvalueCreator = VarInitValueAndAddressOfCreator(namespace) initvalueCreator.process(this) @@ -24,6 +26,7 @@ private class StatementReorderer(private val program: Program): IAstProcessor { // - all other subroutines will be moved to the end of their block. // // Also, makes sure any value assignments get the proper type casts if needed to cast them into the target variable's type. + // (this includes function call arguments) private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option") @@ -192,7 +195,6 @@ private class StatementReorderer(private val program: Program): IAstProcessor { statements.addAll(result) } - override fun process(assignment: Assignment): IStatement { val target=assignment.singleTarget if(target!=null) { @@ -210,6 +212,56 @@ private class StatementReorderer(private val program: Program): IAstProcessor { return super.process(assignment) } + override fun process(functionCallStatement: FunctionCallStatement): IStatement { + checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope()) + return super.process(functionCallStatement) + } + + override fun process(functionCall: FunctionCall): IExpression { + checkFunctionCallArguments(functionCall, functionCall.definingScope()) + return super.process(functionCall) + } + + private fun checkFunctionCallArguments(call: IFunctionCall, scope: INameScope) { + // see if a typecast is needed to convert the arguments into the required parameter's type + val sub = call.target.targetStatement(scope) + when(sub) { + is Subroutine -> { + for(arg in sub.parameters.zip(call.arglist.withIndex())) { + val argtype = arg.second.value.resultingDatatype(program) + if(argtype!=null) { + val requiredType = arg.first.type + if (requiredType != argtype) { + if (argtype isAssignableTo requiredType) { + val typecasted = TypecastExpression(arg.second.value, requiredType, arg.second.value.position) + call.arglist[arg.second.index] = typecasted + } + // if they're not assignable, we'll get a proper error later from the AstChecker + } + } + } + } + is BuiltinFunctionStatementPlaceholder -> { + val func = BuiltinFunctions.getValue(sub.name) + for(arg in func.parameters.zip(call.arglist.withIndex())) { + val argtype = arg.second.value.resultingDatatype(program) + if(argtype!=null) { + if(arg.first.possibleDatatypes.any{ argtype == it}) + continue + for(possibleType in arg.first.possibleDatatypes) { + if(argtype isAssignableTo possibleType) { + val typecasted = TypecastExpression(arg.second.value, possibleType, arg.second.value.position) + call.arglist[arg.second.index] = typecasted + break + } + } + } + } + } + else -> TODO("call to something weird $sub") + } + } + private fun sortConstantAssignmentSequence(first: Assignment, stmtIter: MutableIterator): Pair, IStatement?> { val sequence= mutableListOf(first) var trailing: IStatement? = null diff --git a/compiler/src/prog8/astvm/AstVm.kt b/compiler/src/prog8/astvm/AstVm.kt index eaba1afb6..82c6697fb 100644 --- a/compiler/src/prog8/astvm/AstVm.kt +++ b/compiler/src/prog8/astvm/AstVm.kt @@ -87,7 +87,7 @@ class AstVm(val program: Program) { init.process(program) // initialize all global variables - for(m in program.modules) { + for (m in program.modules) { for (b in m.statements.filterIsInstance()) { for (s in b.statements.filterIsInstance()) { if (s.name == initvarsSubName) { @@ -109,7 +109,7 @@ class AstVm(val program: Program) { } println("PROGRAM EXITED!") dialog.title = "PROGRAM EXITED" - } catch(bp: VmBreakpointException) { + } catch (bp: VmBreakpointException) { println("Breakpoint: execution halted. Press enter to resume.") readLine() } catch (tx: VmTerminationException) { @@ -123,15 +123,24 @@ class AstVm(val program: Program) { private val runtimeVariables = RuntimeVariables() private val functions = BuiltinFunctions() - class LoopControlBreak: Exception() - class LoopControlContinue: Exception() - class LoopControlReturn(val returnvalues: List): Exception() + class LoopControlBreak : Exception() + class LoopControlContinue : Exception() + class LoopControlReturn(val returnvalues: List) : Exception() internal fun executeSubroutine(sub: Subroutine, arguments: List): List { assert(!sub.isAsmSubroutine) if (sub.statements.isEmpty()) throw VmTerminationException("scope contains no statements: $sub") - // TODO process arguments if it's a subroutine + if (arguments.size != sub.parameters.size) + throw VmTerminationException("number of args doesn't match number of required parameters") + + for (arg in sub.parameters.zip(arguments)) { + val idref = IdentifierReference(listOf(arg.first.name), sub.position) + performAssignment(AssignTarget(null, idref, null, null, idref.position), + arg.second, sub.statements.first(), + EvalContext(program, mem, runtimeVariables, functions, ::executeSubroutine)) + } + try { for (s in sub.statements) { executeStatement(sub, s) @@ -151,16 +160,16 @@ class AstVm(val program: Program) { private fun executeStatement(sub: INameScope, stmt: IStatement) { val evalCtx = EvalContext(program, mem, runtimeVariables, functions, ::executeSubroutine) instructionCounter++ - if(instructionCounter % 100 == 0) + if (instructionCounter % 100 == 0) Thread.sleep(1) when (stmt) { is NopStatement, is Label, is Subroutine -> { // do nothing, skip this instruction } is Directive -> { - if(stmt.directive=="%breakpoint") + if (stmt.directive == "%breakpoint") throw VmBreakpointException() - else if(stmt.directive=="%asm") + else if (stmt.directive == "%asm") throw VmExecutionException("can't execute assembly code") } is VarDecl -> { @@ -168,14 +177,14 @@ class AstVm(val program: Program) { } is FunctionCallStatement -> { val target = stmt.target.targetStatement(program.namespace) - when(target) { + when (target) { is Subroutine -> { val args = evaluate(stmt.arglist) - if(target.isAsmSubroutine) { + if (target.isAsmSubroutine) { performSyscall(target, args) } else { - val results = executeSubroutine(target, args) - // TODO process result values + executeSubroutine(target, args) + // any return value(s) are discarded } } is BuiltinFunctionStatementPlaceholder -> { @@ -183,7 +192,7 @@ class AstVm(val program: Program) { functions.performBuiltinFunction(target.name, args) } else -> { - TODO("CALL $target") + TODO("weird call $target") } } } @@ -194,101 +203,31 @@ class AstVm(val program: Program) { is Continue -> throw LoopControlContinue() is Break -> throw LoopControlBreak() is Assignment -> { - if(stmt.aug_op!=null) + if (stmt.aug_op != null) throw VmExecutionException("augmented assignment should have been converted into regular one $stmt") val target = stmt.singleTarget - if(target!=null) { + if (target != null) { val value = evaluate(stmt.value, evalCtx) - when { - target.identifier!=null -> { - val decl = stmt.definingScope().lookup(target.identifier.nameInSource, stmt) as? VarDecl - ?: throw VmExecutionException("can't find assignment target ${target.identifier}") - if(decl.type==VarDeclType.MEMORY) { - val address = runtimeVariables.getMemoryAddress(decl.definingScope(), decl.name) - when(decl.datatype) { - DataType.UBYTE -> mem.setUByte(address, value.byteval!!) - DataType.BYTE -> mem.setSByte(address, value.byteval!!) - DataType.UWORD -> mem.setUWord(address, value.wordval!!) - DataType.WORD -> mem.setSWord(address, value.wordval!!) - DataType.FLOAT -> mem.setFloat(address, value.floatval!!) - DataType.STR -> mem.setString(address, value.str!!) - DataType.STR_S -> mem.setScreencodeString(address, value.str!!) - else -> TODO("set memvar $decl") - } - } else - runtimeVariables.set(decl.definingScope(), decl.name, value) - } - target.memoryAddress!=null -> { - TODO("assign memory $stmt") - } - target.arrayindexed!=null -> { - val array = evaluate(target.arrayindexed.identifier, evalCtx) - val index = evaluate(target.arrayindexed.arrayspec.index, evalCtx) - 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") - } - DataType.STR, DataType.STR_S -> { - if(value.type !in ByteDatatypes) - throw VmExecutionException("new value is of different datatype ${value.type} for $array") - } - else -> throw VmExecutionException("strange array type ${array.type}") - } - if(array.type in ArrayDatatypes) - array.array!![index.integerValue()] = value.numericValue() - else if(array.type in StringDatatypes) { - val indexInt = index.integerValue() - val newchr = Petscii.decodePetscii(listOf(value.numericValue().toShort()), true) - val newstr = array.str!!.replaceRange(indexInt, indexInt+1, newchr) - val ident = stmt.definingScope().lookup(target.arrayindexed.identifier.nameInSource, stmt) as? VarDecl - ?: throw VmExecutionException("can't find assignment target ${target.identifier}") - val identScope = ident.definingScope() - program.heap.update(array.heapId!!, newstr) - runtimeVariables.set(identScope, ident.name, RuntimeValue(array.type, str=newstr, heapId=array.heapId)) - } - } - target.register!=null -> { - runtimeVariables.set(program.namespace, target.register.name, value) - } - else -> TODO("assign $target") - } - } - else TODO("assign multitarget $stmt") + performAssignment(target, value, stmt, evalCtx) + } else TODO("assign multitarget $stmt") } is PostIncrDecr -> { when { - stmt.target.identifier!=null -> { + stmt.target.identifier != null -> { val ident = stmt.definingScope().lookup(stmt.target.identifier!!.nameInSource, stmt) as VarDecl val identScope = ident.definingScope() var value = runtimeVariables.get(identScope, ident.name) value = when { - stmt.operator=="++" -> value.add(RuntimeValue(value.type, 1)) - stmt.operator=="--" -> value.sub(RuntimeValue(value.type, 1)) + stmt.operator == "++" -> value.add(RuntimeValue(value.type, 1)) + stmt.operator == "--" -> value.sub(RuntimeValue(value.type, 1)) else -> throw VmExecutionException("strange postincdec operator $stmt") } runtimeVariables.set(identScope, ident.name, value) } - stmt.target.memoryAddress!=null -> { + stmt.target.memoryAddress != null -> { TODO("postincrdecr memory $stmt") } - stmt.target.arrayindexed!=null -> { + stmt.target.arrayindexed != null -> { TODO("postincrdecr array $stmt") } } @@ -297,8 +236,8 @@ class AstVm(val program: Program) { TODO("jump $stmt") } is InlineAssembly -> { - if(sub is Subroutine) { - when(sub.scopedname) { + if (sub is Subroutine) { + when (sub.scopedname) { "c64flt.print_f" -> { val arg = runtimeVariables.get(sub, sub.parameters.single().name) performSyscall(sub, listOf(arg)) @@ -312,7 +251,7 @@ class AstVm(val program: Program) { is AnonymousScope -> executeAnonymousScope(stmt) is IfStatement -> { val condition = evaluate(stmt.condition, evalCtx) - if(condition.asBoolean) + if (condition.asBoolean) executeAnonymousScope(stmt.truepart) else executeAnonymousScope(stmt.elsepart) @@ -326,16 +265,15 @@ class AstVm(val program: Program) { throw VmExecutionException("can only iterate over an iterable value: $stmt") val loopvarDt: DataType val loopvar: IdentifierReference - if(stmt.loopRegister!=null) { + if (stmt.loopRegister != null) { loopvarDt = DataType.UBYTE loopvar = IdentifierReference(listOf(stmt.loopRegister.name), stmt.position) - } - else { + } else { loopvarDt = stmt.loopVar!!.resultingDatatype(program)!! loopvar = stmt.loopVar } val iterator = iterable.iterator() - for(loopvalue in iterator) { + for (loopvalue in iterator) { try { oneForCycle(stmt, loopvarDt, loopvalue, loopvar) } catch (b: LoopControlBreak) { @@ -351,9 +289,9 @@ class AstVm(val program: Program) { try { executeAnonymousScope(stmt.body) condition = evaluate(stmt.condition, evalCtx) - } catch(b: LoopControlBreak) { + } catch (b: LoopControlBreak) { break - } catch(c: LoopControlContinue){ + } catch (c: LoopControlContinue) { continue } } @@ -363,12 +301,12 @@ class AstVm(val program: Program) { val condition = evaluate(stmt.untilCondition, evalCtx) try { executeAnonymousScope(stmt.body) - } catch(b: LoopControlBreak) { + } catch (b: LoopControlBreak) { break - } catch(c: LoopControlContinue){ + } catch (c: LoopControlContinue) { continue } - } while(!condition.asBoolean) + } while (!condition.asBoolean) } else -> { TODO("implement $stmt") @@ -376,37 +314,101 @@ class AstVm(val program: Program) { } } - private fun oneForCycle(stmt: ForLoop, loopvarDt: DataType, loopValue: Number, loopVar: IdentifierReference) { - val value: LiteralValue = - when (loopvarDt) { - DataType.UBYTE -> LiteralValue(DataType.UBYTE, loopValue.toShort(), position = stmt.position) - DataType.BYTE -> LiteralValue(DataType.BYTE, loopValue.toShort(), position = stmt.position) - DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = (loopValue as Int), position = stmt.position) - DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = (loopValue as Int), position = stmt.position) - DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = (loopValue as Double), position = stmt.position) - else -> TODO("weird loopvar type $loopvarDt") + fun performAssignment(target: AssignTarget, value: RuntimeValue, contextStmt: IStatement, evalCtx: EvalContext) { + when { + target.identifier != null -> { + val decl = contextStmt.definingScope().lookup(target.identifier.nameInSource, contextStmt) as? VarDecl + ?: throw VmExecutionException("can't find assignment target ${target.identifier}") + if (decl.type == VarDeclType.MEMORY) { + val address = runtimeVariables.getMemoryAddress(decl.definingScope(), decl.name) + when (decl.datatype) { + DataType.UBYTE -> mem.setUByte(address, value.byteval!!) + DataType.BYTE -> mem.setSByte(address, value.byteval!!) + DataType.UWORD -> mem.setUWord(address, value.wordval!!) + DataType.WORD -> mem.setSWord(address, value.wordval!!) + DataType.FLOAT -> mem.setFloat(address, value.floatval!!) + DataType.STR -> mem.setString(address, value.str!!) + DataType.STR_S -> mem.setScreencodeString(address, value.str!!) + else -> TODO("set memvar $decl") + } + } else + runtimeVariables.set(decl.definingScope(), decl.name, value) + } + target.memoryAddress != null -> { + TODO("assign memory") + } + target.arrayindexed != null -> { + val array = evaluate(target.arrayindexed.identifier, evalCtx) + val index = evaluate(target.arrayindexed.arrayspec.index, evalCtx) + 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") + } + DataType.STR, DataType.STR_S -> { + if (value.type !in ByteDatatypes) + throw VmExecutionException("new value is of different datatype ${value.type} for $array") + } + else -> throw VmExecutionException("strange array type ${array.type}") } - val assignment = Assignment(listOf(AssignTarget(null, loopVar, null, null, - position = loopVar.position)), null, value, stmt.iterable.position) - assignment.linkParents(stmt.body) - executeStatement(stmt.body, assignment) // assign the new loop value to the loopvar - executeAnonymousScope(stmt.body) // and run the code + if (array.type in ArrayDatatypes) + array.array!![index.integerValue()] = value.numericValue() + else if (array.type in StringDatatypes) { + val indexInt = index.integerValue() + val newchr = Petscii.decodePetscii(listOf(value.numericValue().toShort()), true) + val newstr = array.str!!.replaceRange(indexInt, indexInt + 1, newchr) + val ident = contextStmt.definingScope().lookup(target.arrayindexed.identifier.nameInSource, contextStmt) as? VarDecl + ?: throw VmExecutionException("can't find assignment target ${target.identifier}") + val identScope = ident.definingScope() + program.heap.update(array.heapId!!, newstr) + runtimeVariables.set(identScope, ident.name, RuntimeValue(array.type, str = newstr, heapId = array.heapId)) + } + } + target.register != null -> { + runtimeVariables.set(program.namespace, target.register.name, value) + } + else -> TODO("assign $target") + } } - private fun evaluate(args: List): List = args.map { - evaluate(it, EvalContext(program, mem, runtimeVariables, functions, ::executeSubroutine)) + private fun oneForCycle(stmt: ForLoop, loopvarDt: DataType, loopValue: Number, loopVar: IdentifierReference) { + // assign the new loop value to the loopvar, and run the code + performAssignment(AssignTarget(null, loopVar, null, null, loopVar.position), + RuntimeValue(loopvarDt, loopValue), stmt.body.statements.first(), + EvalContext(program, mem, runtimeVariables, functions, ::executeSubroutine)) + executeAnonymousScope(stmt.body) + } + + private fun evaluate(args: List): List { + val ctx = EvalContext(program, mem, runtimeVariables, functions, ::executeSubroutine) + return args.map { evaluate(it, ctx) } } private fun performSyscall(sub: Subroutine, args: List) { assert(sub.isAsmSubroutine) - when(sub.scopedname) { + 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) { + if (args[0].wordval != null) { val str = program.heap.get(args[0].wordval!!).str!! dialog.canvas.printText(str, 1, true) - } - else + } else dialog.canvas.printText(args[0].str!!, 1, true) } "c64scr.print_ub" -> { @@ -416,7 +418,7 @@ class AstVm(val program: Program) { dialog.canvas.printText(args[0].byteval!!.toString(), 1, true) } "c64scr.print_ubhex" -> { - val prefix = if(args[0].asBoolean) "$" else "" + val prefix = if (args[0].asBoolean) "$" else "" val number = args[1].byteval!! dialog.canvas.printText("$prefix${number.toString(16).padStart(2, '0')}", 1, true) } @@ -427,7 +429,7 @@ class AstVm(val program: Program) { dialog.canvas.printText(args[0].wordval!!.toString(), 1, true) } "c64scr.print_uwhex" -> { - val prefix = if(args[0].asBoolean) "$" else "" + val prefix = if (args[0].asBoolean) "$" else "" val number = args[1].wordval!! dialog.canvas.printText("$prefix${number.toString(16).padStart(4, '0')}", 1, true) } @@ -441,34 +443,33 @@ class AstVm(val program: Program) { } } - private fun setFlags(value: LiteralValue?) { - if(value!=null) { - when(value.type) { + if (value != null) { + when (value.type) { DataType.UBYTE -> { val v = value.bytevalue!!.toInt() - P_negative = v>127 - P_zero = v==0 + P_negative = v > 127 + P_zero = v == 0 } DataType.BYTE -> { val v = value.bytevalue!!.toInt() - P_negative = v<0 - P_zero = v==0 + P_negative = v < 0 + P_zero = v == 0 } DataType.UWORD -> { val v = value.wordvalue!! - P_negative = v>32767 - P_zero = v==0 + P_negative = v > 32767 + P_zero = v == 0 } DataType.WORD -> { val v = value.wordvalue!! - P_negative = v<0 - P_zero = v==0 + P_negative = v < 0 + P_zero = v == 0 } DataType.FLOAT -> { val flt = value.floatvalue!! P_negative = flt < 0.0 - P_zero = flt==0.0 + P_zero = flt == 0.0 } else -> { // no flags for non-numeric type @@ -477,3 +478,4 @@ class AstVm(val program: Program) { } } } + diff --git a/compiler/src/prog8/compiler/RuntimeValue.kt b/compiler/src/prog8/compiler/RuntimeValue.kt index 65364fcf9..7a224fc8d 100644 --- a/compiler/src/prog8/compiler/RuntimeValue.kt +++ b/compiler/src/prog8/compiler/RuntimeValue.kt @@ -93,6 +93,19 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?= } } + fun asLiteralValue(): LiteralValue { + return when(type) { + in ByteDatatypes -> LiteralValue(type, byteval, position = Position("", 0, 0, 0)) + in WordDatatypes -> LiteralValue(type, wordvalue = wordval, position = Position("", 0, 0, 0)) + DataType.FLOAT -> LiteralValue(type, floatvalue = floatval, position = Position("", 0, 0, 0)) + in StringDatatypes -> LiteralValue(type, strvalue = str, position = Position("", 0, 0, 0)) + in ArrayDatatypes -> LiteralValue(type, + arrayvalue = array?.map { LiteralValue.optimalNumeric(it, Position("", 0, 0, 0)) }?.toTypedArray(), + position = Position("", 0, 0, 0)) + else -> TODO("strange type") + } + } + override fun toString(): String { return when(type) { DataType.UBYTE -> "ub:%02x".format(byteval) diff --git a/examples/test.p8 b/examples/test.p8 index d36309ba2..16c7410ea 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -6,6 +6,28 @@ ~ main { sub start() { + ubyte u1 = 100 + ubyte u2 = 30 + byte ub = -30 + + abs(ub) + word r = moo(u1,u2) + c64scr.print_w(r) + + } + + sub moo(ubyte p1, word p2) -> word { + c64scr.print_ub(p1) + c64.CHROUT(',') + c64scr.print_w(p2) + c64.CHROUT('\n') + c64.CHROUT('\n') + + for word ww in 200 to 300 step 13 { + c64scr.print_w(ww) + c64.CHROUT('\n') + } + ubyte u1 = 100 ubyte u2 = 30 @@ -17,5 +39,6 @@ c64.CHROUT('\n') c64scr.print_ub(u2 * 7) c64.CHROUT('\n') + return 12345 } }