Compare commits

...

24 Commits
v1.9 ... v1.10

Author SHA1 Message Date
7c9b8f7d43 cleaned up some buildprocess scripts 2019-07-11 17:27:57 +02:00
845a99d623 return statement only has one single possible value
astvm can now more or less run all examples
2019-07-10 19:27:44 +02:00
3d7a4bf81a astvm can now more or less run all examples 2019-07-10 18:44:54 +02:00
d4b3e35bd2 astvm almost complete 2019-07-10 16:50:41 +02:00
a59f7c75dc fixed some compile time and vm arithmetic errors 2019-07-10 13:33:52 +02:00
44fe2369d6 multitarget assignments removed 2019-07-10 10:11:37 +02:00
aaaab2cfcf fix asm gen for loops when dealing with registers as loopvar 2019-07-10 08:51:05 +02:00
9a3dab20dc extra warnings about register usage in loops 2019-07-10 08:30:17 +02:00
20379b5927 fixed astvm postincrdecr and rsave/rrestore 2019-07-10 08:13:42 +02:00
34dcce67e4 fixed petscii conversion when printing text 2019-07-10 07:10:34 +02:00
0c7f107d01 fix irq routine removal 2019-07-10 03:57:03 +02:00
1f89571aa5 proper NOP removal 2019-07-10 03:06:31 +02:00
7eed1ebbf8 optimized typecasting more 2019-07-10 02:54:39 +02:00
12cb7d7abe optimize redundant typecasts more 2019-07-10 01:52:04 +02:00
c9b16dcbd9 nicer printing of arrays, fix inc/dec overflow issue in runtimevalue 2019-07-10 01:16:32 +02:00
dcab6d00bb ver 2019-07-10 00:50:18 +02:00
a85743f241 docs about 'when' statement 2019-07-10 00:45:53 +02:00
14cabde5cf when statement extended with multiple choice values 2019-07-10 00:25:21 +02:00
cc078503e3 tehtriz example uses when statement 2019-07-09 23:39:03 +02:00
2a0c3377f9 fixed Nop statements without parent 2019-07-09 23:27:09 +02:00
16454f5560 optimized when asm 2019-07-09 21:59:50 +02:00
c1343a78f1 when working correctly in asm (corrected dup & cmp) 2019-07-09 21:41:47 +02:00
9d0c65c682 when working correctly in stackvm and astvm 2019-07-09 20:39:08 +02:00
9e6408244f fix scoping of variables in when statement 2019-07-09 19:44:59 +02:00
54 changed files with 2476 additions and 2009 deletions

View File

@ -21,6 +21,7 @@ which aims to provide many conveniences over raw assembly code (even when using
- automatic variable allocations, automatic string variables and string sharing
- constant folding in expressions (compile-time evaluation)
- conditional branches
- when statement to provide a 'jump table' alternative to if/elseif chains
- automatic type conversions
- floating point operations (uses the C64 Basic ROM routines for this)
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses

View File

@ -1 +1 @@
1.9
1.10

View File

@ -80,6 +80,7 @@ interface INameScope {
val subscopes = mutableMapOf<String, INameScope>()
for(stmt in statements) {
when(stmt) {
// NOTE: if other nodes are introduced that are a scope of contain subscopes, they must be added here!
is INameScope -> subscopes[stmt.name] = stmt
is ForLoop -> subscopes[stmt.body.name] = stmt.body
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body
@ -94,13 +95,17 @@ interface INameScope {
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
}
is WhenStatement -> {
stmt.choices.forEach { subscopes[it.statements.name] = it.statements }
}
}
}
return subscopes
}
fun getLabelOrVariable(name: String): IStatement? {
// TODO this is called A LOT and could perhaps be optimized a bit more, but adding a cache didn't make much of a practical runtime difference
// this is called A LOT and could perhaps be optimized a bit more,
// but adding a memoization cache didn't make much of a practical runtime difference
for (stmt in statements) {
if (stmt is VarDecl && stmt.name==name) return stmt
if (stmt is Label && stmt.name==name) return stmt
@ -169,7 +174,7 @@ interface IExpression: Node {
fun constValue(program: Program): LiteralValue?
fun accept(visitor: IAstModifyingVisitor): IExpression
fun accept(visitor: IAstVisitor)
fun referencesIdentifier(name: String): Boolean
fun referencesIdentifiers(vararg name: String): Boolean
fun inferType(program: Program): DataType?
infix fun isSameAs(other: IExpression): Boolean {

View File

@ -113,11 +113,11 @@ private fun prog8Parser.StatementContext.toAst() : IStatement {
}
assignment()?.let {
return Assignment(it.assign_targets().toAst(), null, it.expression().toAst(), it.toPosition())
return Assignment(it.assign_target().toAst(), null, it.expression().toAst(), it.toPosition())
}
augassignment()?.let {
return Assignment(listOf(it.assign_target().toAst()),
return Assignment(it.assign_target().toAst(),
it.operator.text,
it.expression().toAst(),
it.toPosition())
@ -178,9 +178,6 @@ private fun prog8Parser.StatementContext.toAst() : IStatement {
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
}
private fun prog8Parser.Assign_targetsContext.toAst(): List<AssignTarget> = assign_target().map { it.toAst() }
private fun prog8Parser.AsmsubroutineContext.toAst(): IStatement {
val name = identifier().text
val address = asmsub_address()?.address?.toAst()?.number?.toInt()
@ -249,8 +246,7 @@ private fun prog8Parser.InlineasmContext.toAst() =
private fun prog8Parser.ReturnstmtContext.toAst() : Return {
val values = expression_list()
return Return(values?.toAst() ?: emptyList(), toPosition())
return Return(expression()?.toAst(), toPosition())
}
private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
@ -554,13 +550,13 @@ private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {
}
private fun prog8Parser.When_choiceContext.toAst(): WhenChoice {
val value = expression()?.toAst()
val values = expression_list()?.toAst()
val stmt = statement()?.toAst()
val stmt_block = statement_block()?.toAst()?.toMutableList() ?: mutableListOf()
if(stmt!=null)
stmt_block.add(stmt)
val scope = AnonymousScope(stmt_block, toPosition())
return WhenChoice(value, scope, toPosition())
return WhenChoice(values, scope, toPosition())
}
internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")

View File

@ -38,12 +38,19 @@ enum class DataType {
infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it }
infix fun biggerThan(other: DataType) =
infix fun largerThan(other: DataType) =
when(this) {
in ByteDatatypes -> false
in WordDatatypes -> other in ByteDatatypes
else -> true
}
infix fun equalsSize(other: DataType) =
when(this) {
in ByteDatatypes -> other in ByteDatatypes
in WordDatatypes -> other in WordDatatypes
else -> false
}
}
enum class Register {

View File

@ -6,6 +6,7 @@ import prog8.ast.processing.*
import prog8.ast.statements.Assignment
import prog8.ast.statements.ForLoop
import prog8.compiler.CompilationOptions
import prog8.optimizer.RemoveNops
// the name of the subroutine that should be called for every block to initialize its variables
@ -16,6 +17,12 @@ internal const val initvarsSubName="prog8_init_vars"
internal const val autoHeapValuePrefix = "auto_heap_value_"
internal fun Program.removeNops() {
val remover = RemoveNops()
remover.visit(this)
}
internal fun Program.checkValid(compilerOptions: CompilationOptions) {
val checker = AstChecker(this, compilerOptions)
checker.visit(this)
@ -54,6 +61,7 @@ internal fun Program.checkIdentifiers() {
// add any anonymous variables for heap values that are used,
// and replace an iterable literalvalue by identifierref to new local variable
// TODO: this is't doing anything anymore?
for (variable in checker.anonymousVariablesFromHeap.values) {
val scope = variable.first.definingScope()
scope.statements.add(variable.second)

View File

@ -1,6 +1,7 @@
package prog8.ast.expressions
import prog8.ast.*
import prog8.ast.antlr.escape
import prog8.ast.base.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor
@ -12,7 +13,6 @@ import prog8.functions.BuiltinFunctions
import prog8.functions.NotConstArgumentException
import prog8.functions.builtinFunctionReturnType
import kotlin.math.abs
import kotlin.math.floor
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
@ -29,7 +29,7 @@ class PrefixExpression(val operator: String, var expression: IExpression, overri
override fun constValue(program: Program): LiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name)
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? = expression.inferType(program)
override fun toString(): String {
@ -55,7 +55,7 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String) = left.referencesIdentifier(name) || right.referencesIdentifier(name)
override fun referencesIdentifiers(vararg name: String) = left.referencesIdentifiers(*name) || right.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? {
val leftDt = left.inferType(program)
val rightDt = right.inferType(program)
@ -223,7 +223,7 @@ class ArrayIndexedExpression(val identifier: IdentifierReference,
override fun constValue(program: Program): LiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String) = identifier.referencesIdentifier(name)
override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? {
val target = identifier.targetStatement(program.namespace)
@ -257,7 +257,7 @@ class TypecastExpression(var expression: IExpression, var type: DataType, val im
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name)
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? = type
override fun constValue(program: Program): LiteralValue? {
val cv = expression.constValue(program) ?: return null
@ -281,7 +281,7 @@ data class AddressOf(val identifier: IdentifierReference, override val position:
var scopedname: String? = null // will be set in a later state by the compiler
override fun constValue(program: Program): LiteralValue? = null
override fun referencesIdentifier(name: String) = false
override fun referencesIdentifiers(vararg name: String) = false
override fun inferType(program: Program) = DataType.UWORD
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
@ -297,7 +297,7 @@ class DirectMemoryRead(var addressExpression: IExpression, override val position
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String) = false
override fun referencesIdentifiers(vararg name: String) = false
override fun inferType(program: Program): DataType? = DataType.UBYTE
override fun constValue(program: Program): LiteralValue? = null
@ -316,7 +316,7 @@ open class LiteralValue(val type: DataType,
override val position: Position) : IExpression {
override lateinit var parent: Node
override fun referencesIdentifier(name: String) = arrayvalue?.any { it.referencesIdentifier(name) } ?: false
override fun referencesIdentifiers(vararg name: String) = arrayvalue?.any { it.referencesIdentifiers(*name) } ?: false
val isString = type in StringDatatypes
val isNumeric = type in NumericDatatypes
@ -424,7 +424,7 @@ open class LiteralValue(val type: DataType,
DataType.UWORD -> "uword:$wordvalue"
DataType.WORD -> "word:$wordvalue"
DataType.FLOAT -> "float:$floatvalue"
in StringDatatypes -> "str:$strvalue"
in StringDatatypes -> "str:'${escape(strvalue?:"")}'"
in ArrayDatatypes -> "array:$arrayvalue"
else -> throw FatalAstException("weird datatype")
}
@ -518,17 +518,15 @@ open class LiteralValue(val type: DataType,
return LiteralValue(targettype, floatvalue = wordvalue!!.toDouble(), position = position)
}
DataType.FLOAT -> {
if(floor(floatvalue!!) ==floatvalue) {
val value = floatvalue.toInt()
if (targettype == DataType.BYTE && value in -128..127)
return LiteralValue(targettype, bytevalue = value.toShort(), position = position)
if (targettype == DataType.UBYTE && value in 0..255)
return LiteralValue(targettype, bytevalue = value.toShort(), position = position)
if (targettype == DataType.WORD && value in -32768..32767)
return LiteralValue(targettype, wordvalue = value, position = position)
if (targettype == DataType.UWORD && value in 0..65535)
return LiteralValue(targettype, wordvalue = value, position = position)
}
val value = floatvalue!!.toInt()
if (targettype == DataType.BYTE && value in -128..127)
return LiteralValue(targettype, bytevalue = value.toShort(), position = position)
if (targettype == DataType.UBYTE && value in 0..255)
return LiteralValue(targettype, bytevalue = value.toShort(), position = position)
if (targettype == DataType.WORD && value in -32768..32767)
return LiteralValue(targettype, wordvalue = value, position = position)
if (targettype == DataType.UWORD && value in 0..65535)
return LiteralValue(targettype, wordvalue = value, position = position)
}
in StringDatatypes -> {
if(targettype in StringDatatypes)
@ -585,7 +583,7 @@ class RangeExpr(var from: IExpression,
override fun constValue(program: Program): LiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String): Boolean = from.referencesIdentifier(name) || to.referencesIdentifier(name)
override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? {
val fromDt=from.inferType(program)
val toDt=to.inferType(program)
@ -654,7 +652,7 @@ class RegisterExpr(val register: Register, override val position: Position) : IE
override fun constValue(program: Program): LiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String): Boolean = false
override fun referencesIdentifiers(vararg name: String): Boolean = register.name in name
override fun toString(): String {
return "RegisterExpr(register=$register, pos=$position)"
}
@ -696,7 +694,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String): Boolean = nameInSource.last() == name // @todo is this correct all the time?
override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name // @todo is this correct all the time?
override fun inferType(program: Program): DataType? {
val targetStmt = targetStatement(program.namespace)
@ -762,7 +760,7 @@ class FunctionCall(override var target: IdentifierReference,
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String): Boolean = target.referencesIdentifier(name) || arglist.any{it.referencesIdentifier(name)}
override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || arglist.any{it.referencesIdentifiers(*name)}
override fun inferType(program: Program): DataType? {
val constVal = constValue(program ,false)

View File

@ -90,20 +90,20 @@ internal class AstChecker(private val program: Program,
override fun visit(returnStmt: Return): IStatement {
val expectedReturnValues = returnStmt.definingSubroutine()?.returntypes ?: emptyList()
if(expectedReturnValues.size != returnStmt.values.size) {
// if the return value is a function call, check the result of that call instead
if(returnStmt.values.size==1 && returnStmt.values[0] is FunctionCall) {
val dt = (returnStmt.values[0] as FunctionCall).inferType(program)
if(dt!=null && expectedReturnValues.isEmpty())
checkResult.add(SyntaxError("invalid number of return values", returnStmt.position))
} else
checkResult.add(SyntaxError("invalid number of return values", returnStmt.position))
if(expectedReturnValues.size>1) {
throw AstException("cannot use a return with one value in a subroutine that has multiple return values: $returnStmt")
}
for (rv in expectedReturnValues.withIndex().zip(returnStmt.values)) {
val valueDt=rv.second.inferType(program)
if(rv.first.value!=valueDt)
checkResult.add(ExpressionError("type $valueDt of return value #${rv.first.index + 1} doesn't match subroutine return type ${rv.first.value}", rv.second.position))
if(expectedReturnValues.isEmpty() && returnStmt.value!=null) {
checkResult.add(SyntaxError("invalid number of return values", returnStmt.position))
}
if(expectedReturnValues.isNotEmpty() && returnStmt.value==null) {
checkResult.add(SyntaxError("invalid number of return values", returnStmt.position))
}
if(expectedReturnValues.size==1 && returnStmt.value!=null) {
val valueDt = returnStmt.value!!.inferType(program)
if(expectedReturnValues[0]!=valueDt)
checkResult.add(ExpressionError("type $valueDt of return value doesn't match subroutine's return type", returnStmt.value!!.position))
}
return super.visit(returnStmt)
}
@ -117,7 +117,7 @@ internal class AstChecker(private val program: Program,
checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position))
} else {
if (forLoop.loopRegister != null) {
printWarning("using a register as loop variable is risky (it could get clobbered in the body)", forLoop.position)
printWarning("using a register as loop variable is risky (it could get clobbered)", forLoop.position)
// loop register
if (iterableDt != DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt !in StringDatatypes)
checkResult.add(ExpressionError("register can only loop over bytes", forLoop.position))
@ -126,7 +126,6 @@ internal class AstChecker(private val program: Program,
val loopvar = forLoop.loopVar!!.targetVarDecl(program.namespace)
if(loopvar==null || loopvar.type== VarDeclType.CONST) {
checkResult.add(SyntaxError("for loop requires a variable to loop with", forLoop.position))
} else {
when (loopvar.datatype) {
DataType.UBYTE -> {
@ -319,6 +318,18 @@ internal class AstChecker(private val program: Program,
return subroutine
}
override fun visit(repeatLoop: RepeatLoop): IStatement {
if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y"))
printWarning("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position)
return super.visit(repeatLoop)
}
override fun visit(whileLoop: WhileLoop): IStatement {
if(whileLoop.condition.referencesIdentifiers("A", "X", "Y"))
printWarning("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position)
return super.visit(whileLoop)
}
/**
* Assignment target must be register, or a variable name
* Also check data type compatibility and number of values
@ -328,25 +339,19 @@ internal class AstChecker(private val program: Program,
// assigning from a functioncall COULD return multiple values (from an asm subroutine)
if(assignment.value is FunctionCall) {
val stmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
if (stmt is Subroutine) {
if (stmt.isAsmSubroutine) {
if (stmt.returntypes.size != assignment.targets.size)
checkResult.add(ExpressionError("number of return values doesn't match number of assignment targets", assignment.value.position))
else {
for (thing in stmt.returntypes.zip(assignment.targets)) {
if (thing.second.inferType(program, assignment) != thing.first)
checkResult.add(ExpressionError("return type mismatch for target ${thing.second.shortString()}", assignment.value.position))
}
if (stmt is Subroutine && stmt.isAsmSubroutine) {
if(stmt.returntypes.size>1)
checkResult.add(ExpressionError("It's not possible to store the multipel results of this asmsub call; you should use a small block of custom inline assembly for this.", assignment.value.position))
else {
if(stmt.returntypes.single()!=assignment.target.inferType(program, assignment)) {
checkResult.add(ExpressionError("return type mismatch", assignment.value.position))
}
} else if(assignment.targets.size>1)
checkResult.add(ExpressionError("only asmsub subroutines can return multiple values", assignment.value.position))
}
}
}
var resultingAssignment = assignment
for (target in assignment.targets) {
resultingAssignment = processAssignmentTarget(resultingAssignment, target)
}
resultingAssignment = processAssignmentTarget(resultingAssignment, assignment.target)
return super.visit(resultingAssignment)
}
@ -392,7 +397,7 @@ internal class AstChecker(private val program: Program,
val expression = BinaryExpression(newTarget, assignment.aug_op.substringBeforeLast('='), assignment.value, assignment.position)
expression.linkParents(assignment.parent)
val assignment2 = Assignment(listOf(target), null, expression, assignment.position)
val assignment2 = Assignment(target, null, expression, assignment.position)
assignment2.linkParents(assignment.parent)
return assignment2
}
@ -411,18 +416,16 @@ internal class AstChecker(private val program: Program,
} else {
val sourceDatatype: DataType? = assignment.value.inferType(program)
if(sourceDatatype==null) {
if(assignment.targets.size<=1) {
if (assignment.value is FunctionCall) {
val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
if(targetStmt!=null)
checkResult.add(ExpressionError("function call doesn't return a suitable value to use in assignment", assignment.value.position))
}
else
checkResult.add(ExpressionError("assignment value is invalid or has no proper datatype", assignment.value.position))
if (assignment.value is FunctionCall) {
val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
if(targetStmt!=null)
checkResult.add(ExpressionError("function call doesn't return a suitable value to use in assignment", assignment.value.position))
}
else
checkResult.add(ExpressionError("assignment value is invalid or has no proper datatype", assignment.value.position))
}
else
checkAssignmentCompatible(targetDatatype, sourceDatatype, assignment.value, assignment.targets, assignment.position)
checkAssignmentCompatible(targetDatatype, sourceDatatype, assignment.value, assignment.position)
}
}
return assignment
@ -450,7 +453,7 @@ internal class AstChecker(private val program: Program,
}
// the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true) {
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
err("recursive var declaration")
}
@ -782,8 +785,12 @@ internal class AstChecker(private val program: Program,
val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement)
if(targetStatement!=null)
checkFunctionCall(targetStatement, functionCallStatement.arglist, functionCallStatement.position)
if(targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty())
printWarning("result value of subroutine call is discarded", functionCallStatement.position)
if(targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty()) {
if(targetStatement.returntypes.size==1)
printWarning("result value of subroutine call is discarded", functionCallStatement.position)
else
printWarning("result values of subroutine call are discarded", functionCallStatement.position)
}
return super.visit(functionCallStatement)
}
@ -926,14 +933,19 @@ internal class AstChecker(private val program: Program,
}
override fun visit(whenChoice: WhenChoice) {
if(whenChoice.value!=null) {
val constvalue = whenChoice.value.constValue(program)
if (constvalue == null)
checkResult.add(SyntaxError("value of a when choice must be a constant", whenChoice.position))
else if (constvalue.type !in IntegerDatatypes)
checkResult.add(SyntaxError("value of a when choice must be a byte or word", whenChoice.position))
val whenStmt = whenChoice.parent as WhenStatement
if(whenChoice.values!=null) {
val conditionType = whenStmt.condition.inferType(program)
val constvalues = whenChoice.values.map { it.constValue(program) }
for(constvalue in constvalues) {
when {
constvalue == null -> checkResult.add(SyntaxError("choice value must be a constant", whenChoice.position))
constvalue.type !in IntegerDatatypes -> checkResult.add(SyntaxError("choice value must be a byte or word", whenChoice.position))
constvalue.type != conditionType -> checkResult.add(SyntaxError("choice value datatype differs from condition value", whenChoice.position))
}
}
} else {
if(whenChoice !== (whenChoice.parent as WhenStatement).choices.last())
if(whenChoice !== whenStmt.choices.last())
checkResult.add(SyntaxError("else choice must be the last one", whenChoice.position))
}
super.visit(whenChoice)
@ -1171,7 +1183,6 @@ internal class AstChecker(private val program: Program,
private fun checkAssignmentCompatible(targetDatatype: DataType,
sourceDatatype: DataType,
sourceValue: IExpression,
assignTargets: List<AssignTarget>,
position: Position) : Boolean {
if(sourceValue is RangeExpr)
@ -1192,10 +1203,7 @@ internal class AstChecker(private val program: Program,
return true
if((sourceDatatype== DataType.UWORD || sourceDatatype== DataType.WORD) && (targetDatatype== DataType.UBYTE || targetDatatype== DataType.BYTE)) {
if(assignTargets.size==2 && assignTargets[0].register!=null && assignTargets[1].register!=null)
return true // for asm subroutine calls that return a (U)WORD that's going to be stored into two BYTES (registers), we make an exception.
else
checkResult.add(ExpressionError("cannot assign word to byte, use msb() or lsb()?", position))
checkResult.add(ExpressionError("cannot assign word to byte, use msb() or lsb()?", position))
}
else if(sourceDatatype== DataType.FLOAT && targetDatatype in IntegerDatatypes)
checkResult.add(ExpressionError("cannot assign float to ${targetDatatype.name.toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position))

View File

@ -168,25 +168,23 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
}
override fun visit(returnStmt: Return): IStatement {
if(returnStmt.values.isNotEmpty()) {
if(returnStmt.value!=null) {
// possibly adjust any literal values returned, into the desired returning data type
val subroutine = returnStmt.definingSubroutine()!!
if(subroutine.returntypes.size!=returnStmt.values.size)
if(subroutine.returntypes.size!=1)
return returnStmt // mismatch in number of return values, error will be printed later.
val newValues = mutableListOf<IExpression>()
for(returnvalue in returnStmt.values.zip(subroutine.returntypes)) {
val lval = returnvalue.first as? LiteralValue
if(lval!=null) {
val adjusted = lval.cast(returnvalue.second)
if(adjusted!=null && adjusted !== lval)
newValues.add(adjusted)
else
newValues.add(lval)
}
val newValue: IExpression
val lval = returnStmt.value as? LiteralValue
if(lval!=null) {
val adjusted = lval.cast(subroutine.returntypes.single())
if(adjusted!=null && adjusted !== lval)
newValue = adjusted
else
newValues.add(returnvalue.first)
}
returnStmt.values = newValues
newValue = lval
} else
newValue = returnStmt.value!!
returnStmt.value = newValue
}
return super.visit(returnStmt)
}
@ -199,6 +197,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
if(literalValue.heapId!=null && literalValue.parent !is VarDecl) {
// a literal value that's not declared as a variable, which refers to something on the heap.
// we need to introduce an auto-generated variable for this to be able to refer to the value!
// (note: ususally, this has been taken care of already when the var was created)
val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, "$autoHeapValuePrefix${literalValue.heapId}", literalValue,
isArray = false, autoGenerated = false, position = literalValue.position)
anonymousVariablesFromHeap[variable.name] = Pair(literalValue, variable)

View File

@ -111,7 +111,7 @@ interface IAstModifyingVisitor {
}
fun visit(assignment: Assignment): IStatement {
assignment.targets = assignment.targets.map { it.accept(this) }
assignment.target = assignment.target.accept(this)
assignment.value = assignment.value.accept(this)
return assignment
}
@ -149,7 +149,7 @@ interface IAstModifyingVisitor {
}
fun visit(returnStmt: Return): IStatement {
returnStmt.values = returnStmt.values.map { it.accept(this) }
returnStmt.value = returnStmt.value?.accept(this)
return returnStmt
}
@ -213,7 +213,7 @@ interface IAstModifyingVisitor {
}
fun visit(whenChoice: WhenChoice) {
whenChoice.value?.accept(this)
whenChoice.values?.forEach { it.accept(this) }
whenChoice.statements.accept(this)
}
}

View File

@ -80,7 +80,7 @@ interface IAstVisitor {
}
fun visit(assignment: Assignment) {
assignment.targets.forEach { it.accept(this) }
assignment.target.accept(this)
assignment.value.accept(this)
}
@ -111,7 +111,7 @@ interface IAstVisitor {
}
fun visit(returnStmt: Return) {
returnStmt.values.forEach { it.accept(this) }
returnStmt.value?.accept(this)
}
fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
@ -163,7 +163,7 @@ interface IAstVisitor {
}
fun visit(whenChoice: WhenChoice) {
whenChoice.value?.accept(this)
whenChoice.values?.forEach { it.accept(this) }
whenChoice.statements.accept(this)
}
}

View File

@ -6,9 +6,7 @@ import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.initvarsSubName
import prog8.ast.base.printWarning
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.TypecastExpression
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
@ -91,7 +89,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
&& stmtBeforeFirstSub !is Jump
&& stmtBeforeFirstSub !is Subroutine
&& stmtBeforeFirstSub !is BuiltinFunctionStatementPlaceholder) {
val ret = Return(emptyList(), stmtBeforeFirstSub.position)
val ret = Return(null, stmtBeforeFirstSub.position)
ret.linkParents(block)
block.statements.add(block.statements.size - numSubroutinesAtEnd, ret)
}
@ -139,7 +137,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
// and if an assembly block doesn't contain a rts/rti
if(subroutine.asmAddress==null && subroutine.amountOfRtsInAsm()==0) {
if (subroutine.statements.lastOrNull {it !is VarDecl } !is Return) {
val returnStmt = Return(emptyList(), subroutine.position)
val returnStmt = Return(null, subroutine.position)
returnStmt.linkParents(subroutine)
subroutine.statements.add(returnStmt)
}
@ -173,19 +171,16 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
}
override fun visit(assignment: Assignment): IStatement {
val target=assignment.singleTarget
if(target!=null) {
// see if a typecast is needed to convert the value's type into the proper target type
val valuetype = assignment.value.inferType(program)
val targettype = target.inferType(program, assignment)
if(targettype!=null && valuetype!=null && valuetype!=targettype) {
if(valuetype isAssignableTo targettype) {
assignment.value = TypecastExpression(assignment.value, targettype, true, assignment.value.position)
assignment.value.linkParents(assignment)
}
// if they're not assignable, we'll get a proper error later from the AstChecker
// see if a typecast is needed to convert the value's type into the proper target type
val valuetype = assignment.value.inferType(program)
val targettype = assignment.target.inferType(program, assignment)
if(targettype!=null && valuetype!=null && valuetype!=targettype) {
if(valuetype isAssignableTo targettype) {
assignment.value = TypecastExpression(assignment.value, targettype, true, assignment.value.position)
assignment.value.linkParents(assignment)
}
} else TODO("multi-target assign")
// if they're not assignable, we'll get a proper error later from the AstChecker
}
return super.visit(assignment)
}
@ -264,7 +259,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
break
}
}
val sorted = sequence.sortedWith(compareBy({it.value.inferType(program)}, {it.singleTarget?.shortString(true)}))
val sorted = sequence.sortedWith(compareBy({it.value.inferType(program)}, {it.target.shortString(true)}))
return Pair(sorted, trailing)
}
@ -277,9 +272,51 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
}
override fun visit(whenStatement: WhenStatement): IStatement {
// sort the choices in low-to-high value order
// make sure all choices are just for one single value
val choices = whenStatement.choices.toList()
for(choice in choices) {
if(choice.values==null || choice.values.size==1)
continue
for(v in choice.values) {
val newchoice=WhenChoice(listOf(v), choice.statements, choice.position)
newchoice.parent = choice.parent
whenStatement.choices.add(newchoice)
}
whenStatement.choices.remove(choice)
}
// sort the choices in low-to-high value order (nulls last)
whenStatement.choices
.sortWith(compareBy<WhenChoice, Int?>(nullsLast(), {it.value?.constValue(program)?.asIntegerValue}))
.sortWith(compareBy<WhenChoice, Int?>(nullsLast(), {it.values?.single()?.constValue(program)?.asIntegerValue}))
return super.visit(whenStatement)
}
override fun visit(memread: DirectMemoryRead): IExpression {
// make sure the memory address is an uword
val dt = memread.addressExpression.inferType(program)
if(dt!=DataType.UWORD) {
val literaladdr = memread.addressExpression as? LiteralValue
if(literaladdr!=null) {
memread.addressExpression = literaladdr.cast(DataType.UWORD)!!
} else {
memread.addressExpression = TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
memread.addressExpression.parent = memread
}
}
return super.visit(memread)
}
override fun visit(memwrite: DirectMemoryWrite) {
val dt = memwrite.addressExpression.inferType(program)
if(dt!=DataType.UWORD) {
val literaladdr = memwrite.addressExpression as? LiteralValue
if(literaladdr!=null) {
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)!!
} else {
memwrite.addressExpression = TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
memwrite.addressExpression.parent = memwrite
}
}
super.visit(memwrite)
}
}

View File

@ -110,7 +110,6 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
val variable = VarDecl(VarDeclType.VAR, strvalue.type, false, null, autoVarName, strvalue,
isArray = false, autoGenerated = false, position = strvalue.position)
addVarDecl(strvalue.definingScope(), variable)
// println("MADE ANONVAR $variable") // XXX
}
}
}

View File

@ -6,8 +6,6 @@ import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor
import prog8.compiler.HeapValues
import kotlin.math.max
import kotlin.math.min
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : IStatement {
@ -86,24 +84,24 @@ data class Label(val name: String, override val position: Position) : IStatement
val scopedname: String by lazy { makeScopedName(name) }
}
open class Return(var values: List<IExpression>, override val position: Position) : IStatement {
open class Return(var value: IExpression?, override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline = values.any { it !is LiteralValue }
override val expensiveToInline = value!=null && value !is LiteralValue
override fun linkParents(parent: Node) {
this.parent = parent
values.forEach {it.linkParents(this)}
value?.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun toString(): String {
return "Return(values: $values, pos=$position)"
return "Return($value, pos=$position)"
}
}
class ReturnFromIrq(override val position: Position) : Return(emptyList(), position) {
class ReturnFromIrq(override val position: Position) : Return(null, position) {
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
@ -224,14 +222,14 @@ class ArrayIndex(var index: IExpression, override val position: Position) : Node
fun size() = (index as? LiteralValue)?.asIntegerValue
}
open class Assignment(var targets: List<AssignTarget>, val aug_op : String?, var value: IExpression, override val position: Position) : IStatement {
open class Assignment(var target: AssignTarget, val aug_op : String?, var value: IExpression, override val position: Position) : IStatement {
override lateinit var parent: Node
override val expensiveToInline
get() = value !is LiteralValue
override fun linkParents(parent: Node) {
this.parent = parent
targets.forEach { it.linkParents(this) }
this.target.linkParents(this)
value.linkParents(this)
}
@ -239,19 +237,14 @@ open class Assignment(var targets: List<AssignTarget>, val aug_op : String?, var
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun toString(): String {
return("Assignment(augop: $aug_op, targets: $targets, value: $value, pos=$position)")
return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)")
}
val singleTarget: AssignTarget?
get() {
return targets.singleOrNull() // common case
}
}
// This is a special class so the compiler can see if the assignments are for initializing the vars in the scope,
// or just a regular assignment. It may optimize the initialization step from this.
class VariableInitializationAssignment(target: AssignTarget, aug_op: String?, value: IExpression, position: Position)
: Assignment(listOf(target), aug_op, value, position)
: Assignment(target, aug_op, value, position)
data class AssignTarget(val register: Register?,
val identifier: IdentifierReference?,
@ -479,6 +472,14 @@ class NopStatement(override val position: Position): IStatement {
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
companion object {
fun insteadOf(stmt: IStatement): NopStatement {
val nop = NopStatement(stmt.position)
nop.parent = stmt.parent
return nop
}
}
}
// the subroutine class covers both the normal user-defined subroutines,
@ -679,35 +680,40 @@ class WhenStatement(val condition: IExpression,
choices.forEach { it.linkParents(this) }
}
fun choiceValues(program: Program): List<Pair<Int?, WhenChoice>> {
fun choiceValues(program: Program): List<Pair<List<Int>?, WhenChoice>> {
// only gives sensible results when the choices are all valid (constant integers)
return choices
.map {
val cv = it.value?.constValue(program)
if(cv==null)
null to it
else
cv.asNumericValue!!.toInt() to it
}
val result = mutableListOf<Pair<List<Int>?, WhenChoice>>()
for(choice in choices) {
if(choice.values==null)
result.add(null to choice)
else {
val values = choice.values.map { it.constValue(program)?.asNumericValue?.toInt() }
if(values.contains(null))
result.add(null to choice)
else
result.add(values.filterNotNull() to choice)
}
}
return result
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
}
class WhenChoice(val value: IExpression?, // if null, this is the 'else' part
class WhenChoice(val values: List<IExpression>?, // if null, this is the 'else' part
val statements: AnonymousScope,
override val position: Position) : Node {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
value?.linkParents(this)
values?.forEach { it.linkParents(this) }
statements.linkParents(this)
this.parent = parent
}
override fun toString(): String {
return "Choice($value at $position)"
return "Choice($values at $position)"
}
fun accept(visitor: IAstVisitor) = visitor.visit(this)

View File

@ -249,12 +249,21 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
literalValue.isString -> output("\"${escape(literalValue.strvalue!!)}\"")
literalValue.isArray -> {
if(literalValue.arrayvalue!=null) {
var counter = 0
output("[")
scopelevel++
for (v in literalValue.arrayvalue) {
v.accept(this)
if (v !== literalValue.arrayvalue.last())
output(", ")
counter++
if(counter > 16) {
outputln("")
outputi("")
counter=0
}
}
scopelevel--
output("]")
}
}
@ -262,7 +271,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
}
override fun visit(assignment: Assignment) {
assignment.singleTarget!!.accept(this)
assignment.target.accept(this)
if(assignment.aug_op!=null)
output(" ${assignment.aug_op} ")
else
@ -318,11 +327,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
override fun visit(returnStmt: Return) {
output("return ")
for(v in returnStmt.values) {
v.accept(this)
if(v!==returnStmt.values.last())
output(", ")
}
returnStmt.value?.accept(this)
}
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
@ -351,8 +356,9 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
}
override fun visit(typecast: TypecastExpression) {
output("(")
typecast.expression.accept(this)
output(" as ${datatypeString(typecast.type)} ")
output(" as ${datatypeString(typecast.type)}) ")
}
override fun visit(memread: DirectMemoryRead) {
@ -397,11 +403,15 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
}
override fun visit(whenChoice: WhenChoice) {
if(whenChoice.value==null)
if(whenChoice.values==null)
outputi("else -> ")
else {
outputi("")
whenChoice.value.accept(this)
for(value in whenChoice.values) {
value.accept(this)
if(value !== whenChoice.values.last())
output(",")
}
output(" -> ")
}
if(whenChoice.statements.statements.size==1)
@ -411,6 +421,6 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
outputln("")
}
override fun visit(nopStatement: NopStatement) {
TODO("NOP???")
output("; NOP")
}
}

View File

@ -982,7 +982,7 @@ internal class Compiler(private val program: Program) {
} else {
when (arg.second.registerOrPair!!) {
A -> {
val assign = Assignment(listOf(AssignTarget(Register.A, null, null, null, callPosition)), null, arg.first, callPosition)
val assign = Assignment(AssignTarget(Register.A, null, null, null, callPosition), null, arg.first, callPosition)
assign.linkParents(arguments[0].parent)
translate(assign)
}
@ -991,12 +991,12 @@ internal class Compiler(private val program: Program) {
prog.instr(Opcode.RSAVEX)
restoreX = true
}
val assign = Assignment(listOf(AssignTarget(Register.X, null, null, null, callPosition)), null, arg.first, callPosition)
val assign = Assignment(AssignTarget(Register.X, null, null, null, callPosition), null, arg.first, callPosition)
assign.linkParents(arguments[0].parent)
translate(assign)
}
Y -> {
val assign = Assignment(listOf(AssignTarget(Register.Y, null, null, null, callPosition)), null, arg.first, callPosition)
val assign = Assignment(AssignTarget(Register.Y, null, null, null, callPosition), null, arg.first, callPosition)
assign.linkParents(arguments[0].parent)
translate(assign)
}
@ -1012,8 +1012,8 @@ internal class Compiler(private val program: Program) {
DataType.UBYTE -> {
valueA = arg.first
valueX = LiteralValue.optimalInteger(0, callPosition)
val assignA = Assignment(listOf(AssignTarget(Register.A, null, null, null, callPosition)), null, valueA, callPosition)
val assignX = Assignment(listOf(AssignTarget(Register.X, null, null, null, callPosition)), null, valueX, callPosition)
val assignA = Assignment(AssignTarget(Register.A, null, null, null, callPosition), null, valueA, callPosition)
val assignX = Assignment(AssignTarget(Register.X, null, null, null, callPosition), null, valueX, callPosition)
assignA.linkParents(arguments[0].parent)
assignX.linkParents(arguments[0].parent)
translate(assignA)
@ -1035,8 +1035,8 @@ internal class Compiler(private val program: Program) {
DataType.UBYTE -> {
valueA = arg.first
valueY = LiteralValue.optimalInteger(0, callPosition)
val assignA = Assignment(listOf(AssignTarget(Register.A, null, null, null, callPosition)), null, valueA, callPosition)
val assignY = Assignment(listOf(AssignTarget(Register.Y, null, null, null, callPosition)), null, valueY, callPosition)
val assignA = Assignment(AssignTarget(Register.A, null, null, null, callPosition), null, valueA, callPosition)
val assignY = Assignment(AssignTarget(Register.Y, null, null, null, callPosition), null, valueY, callPosition)
assignA.linkParents(arguments[0].parent)
assignY.linkParents(arguments[0].parent)
translate(assignA)
@ -1062,8 +1062,8 @@ internal class Compiler(private val program: Program) {
DataType.UBYTE -> {
valueX = arg.first
valueY = LiteralValue.optimalInteger(0, callPosition)
val assignX = Assignment(listOf(AssignTarget(Register.X, null, null, null, callPosition)), null, valueX, callPosition)
val assignY = Assignment(listOf(AssignTarget(Register.Y, null, null, null, callPosition)), null, valueY, callPosition)
val assignX = Assignment(AssignTarget(Register.X, null, null, null, callPosition), null, valueX, callPosition)
val assignY = Assignment(AssignTarget(Register.Y, null, null, null, callPosition), null, valueY, callPosition)
assignX.linkParents(arguments[0].parent)
assignY.linkParents(arguments[0].parent)
translate(assignX)
@ -1402,15 +1402,8 @@ internal class Compiler(private val program: Program) {
prog.line(stmt.position)
translate(stmt.value)
val assignTarget= stmt.singleTarget
if(assignTarget==null) {
// we're dealing with multiple return values
translateMultiReturnAssignment(stmt)
return
}
val valueDt = stmt.value.inferType(program)
val targetDt = assignTarget.inferType(program, stmt)
val targetDt = stmt.target.inferType(program, stmt)
if(valueDt!=targetDt) {
// convert value to target datatype if possible
// @todo use convertType()????
@ -1461,8 +1454,8 @@ internal class Compiler(private val program: Program) {
throw CompilerException("augmented assignment should have been converted to regular assignment already")
// pop the result value back into the assignment target
val datatype = assignTarget.inferType(program, stmt)!!
popValueIntoTarget(assignTarget, datatype)
val datatype = stmt.target.inferType(program, stmt)!!
popValueIntoTarget(stmt.target, datatype)
}
private fun pushHeapVarAddress(value: IExpression, removeLastOpcode: Boolean) {
@ -1488,20 +1481,6 @@ internal class Compiler(private val program: Program) {
}
}
private fun translateMultiReturnAssignment(stmt: Assignment) {
val targetStmt = (stmt.value as? FunctionCall)?.target?.targetStatement(program.namespace)
if(targetStmt is Subroutine && targetStmt.isAsmSubroutine) {
// this is the only case where multiple assignment targets are allowed: a call to an asmsub with multiple return values
// the return values are already on the stack (the subroutine call puts them there)
if(stmt.targets.size!=targetStmt.asmReturnvaluesRegisters.size)
throw CompilerException("asmsub number of return values doesn't match number of assignment targets ${stmt.position}")
for(target in stmt.targets) {
val dt = target.inferType(program, stmt)
popValueIntoTarget(target, dt!!)
}
} else throw CompilerException("can only use multiple assignment targets on an asmsub call")
}
private fun popValueIntoTarget(assignTarget: AssignTarget, datatype: DataType) {
when {
assignTarget.identifier != null -> {
@ -1538,9 +1517,8 @@ internal class Compiler(private val program: Program) {
private fun translate(stmt: Return) {
// put the return values on the stack, in reversed order. The caller will accept them.
for(value in stmt.values.reversed()) {
translate(value)
}
if(stmt.value!=null)
translate(stmt.value!!)
prog.line(stmt.position)
prog.instr(Opcode.RETURN)
}
@ -1552,17 +1530,19 @@ internal class Compiler(private val program: Program) {
private fun translate(loop: ForLoop) {
if(loop.body.containsNoCodeNorVars()) return
prog.line(loop.position)
val loopVarName: String
val loopVarDt: DataType
val loopVarName: String?
val loopRegister: Register?
val loopvalueDt: DataType
if(loop.loopRegister!=null) {
val reg = loop.loopRegister
loopVarName = reg.name
loopVarDt = DataType.UBYTE
loopVarName = null
loopRegister = loop.loopRegister
loopvalueDt = DataType.UBYTE
} else {
val loopvar = loop.loopVar!!.targetVarDecl(program.namespace)!!
loopVarName = loopvar.scopedname
loopVarDt = loopvar.datatype
loopvalueDt = loopvar.datatype
loopRegister = null
}
if(loop.iterable is RangeExpr) {
@ -1575,7 +1555,7 @@ internal class Compiler(private val program: Program) {
throw CompilerException("loop over just 1 value should have been optimized away")
if((range.last-range.first) % range.step != 0)
throw CompilerException("range first and last must be exactly inclusive")
when (loopVarDt) {
when (loopvalueDt) {
DataType.UBYTE -> {
if (range.first < 0 || range.first > 255 || range.last < 0 || range.last > 255)
throw CompilerException("range out of bounds for ubyte")
@ -1594,7 +1574,7 @@ internal class Compiler(private val program: Program) {
}
else -> throw CompilerException("range must be byte or word")
}
translateForOverConstantRange(loopVarName, loopVarDt, range, loop.body)
translateForOverConstantRange(loopVarName, loopRegister, loopvalueDt, range, loop.body)
} else {
// loop over a range where one or more of the start, last or step values is not a constant
if(loop.loopRegister!=null) {
@ -1613,7 +1593,7 @@ internal class Compiler(private val program: Program) {
val iterableValue = vardecl.value as LiteralValue
if(iterableValue.type !in IterableDatatypes)
throw CompilerException("loop over something that isn't iterable ${loop.iterable}")
translateForOverIterableVar(loop, loopVarDt, iterableValue)
translateForOverIterableVar(loop, loopvalueDt, iterableValue)
}
loop.iterable is LiteralValue -> throw CompilerException("literal value in loop must have been moved to heap already $loop")
else -> throw CompilerException("loopvar is something strange ${loop.iterable}")
@ -1686,7 +1666,7 @@ internal class Compiler(private val program: Program) {
AssignTarget(null, loop.loopVar!!.copy(), null, null, loop.position)
val arrayspec = ArrayIndex(IdentifierReference(listOf(ForLoop.iteratorLoopcounterVarname), loop.position), loop.position)
val assignLv = Assignment(
listOf(assignTarget), null,
assignTarget, null,
ArrayIndexedExpression((loop.iterable as IdentifierReference).copy(), arrayspec, loop.position),
loop.position)
assignLv.linkParents(loop.body)
@ -1706,7 +1686,7 @@ internal class Compiler(private val program: Program) {
continueStmtLabelStack.pop()
}
private fun translateForOverConstantRange(varname: String, varDt: DataType, range: IntProgression, body: AnonymousScope) {
private fun translateForOverConstantRange(varname: String?, loopregister: Register?, varDt: DataType, range: IntProgression, body: AnonymousScope) {
/**
* for LV in start..last { body }
* (and we already know that the range is not empty, and first and last are exactly inclusive.)
@ -1734,7 +1714,9 @@ internal class Compiler(private val program: Program) {
breakStmtLabelStack.push(breakLabel)
prog.instr(opcodePush(varDt), RuntimeValue(varDt, range.first))
prog.instr(opcodePopvar(varDt), callLabel = varname)
val variableLabel = varname ?: loopregister?.name
prog.instr(opcodePopvar(varDt), callLabel = variableLabel)
prog.label(loopLabel)
translate(body)
prog.label(continueLabel)
@ -1742,25 +1724,25 @@ internal class Compiler(private val program: Program) {
when {
range.step in 1..numberOfIncDecsForOptimize -> {
repeat(range.step) {
prog.instr(opcodeIncvar(varDt), callLabel = varname)
prog.instr(opcodeIncvar(varDt), callLabel = variableLabel)
}
}
range.step in -1 downTo -numberOfIncDecsForOptimize -> {
repeat(abs(range.step)) {
prog.instr(opcodeDecvar(varDt), callLabel = varname)
prog.instr(opcodeDecvar(varDt), callLabel = variableLabel)
}
}
range.step>numberOfIncDecsForOptimize -> {
prog.instr(opcodePushvar(varDt), callLabel = varname)
prog.instr(opcodePushvar(varDt), callLabel = variableLabel)
prog.instr(opcodePush(varDt), RuntimeValue(varDt, range.step))
prog.instr(opcodeAdd(varDt))
prog.instr(opcodePopvar(varDt), callLabel = varname)
prog.instr(opcodePopvar(varDt), callLabel = variableLabel)
}
range.step<numberOfIncDecsForOptimize -> {
prog.instr(opcodePushvar(varDt), callLabel = varname)
prog.instr(opcodePushvar(varDt), callLabel = variableLabel)
prog.instr(opcodePush(varDt), RuntimeValue(varDt, abs(range.step)))
prog.instr(opcodeSub(varDt))
prog.instr(opcodePopvar(varDt), callLabel = varname)
prog.instr(opcodePopvar(varDt), callLabel = variableLabel)
}
}
@ -1768,7 +1750,7 @@ internal class Compiler(private val program: Program) {
// optimize for the for loop that counts to 0
prog.instr(if(range.first>0) Opcode.BPOS else Opcode.BNEG, callLabel = loopLabel)
} else {
prog.instr(opcodePushvar(varDt), callLabel = varname)
prog.instr(opcodePushvar(varDt), callLabel = variableLabel)
val checkValue =
when (varDt) {
DataType.UBYTE -> (range.last + range.step) and 255
@ -1827,7 +1809,7 @@ internal class Compiler(private val program: Program) {
AssignTarget(register, null, null, null, range.position)
}
val startAssignment = Assignment(listOf(makeAssignmentTarget()), null, range.from, range.position)
val startAssignment = Assignment(makeAssignmentTarget(), null, range.from, range.position)
startAssignment.linkParents(body)
translate(startAssignment)
@ -2095,24 +2077,20 @@ internal class Compiler(private val program: Program) {
val endOfWhenLabel = makeLabel(whenstmt, "when_end")
val choiceLabels = mutableListOf<String>()
var previousValue = 0
for(choice in whenstmt.choiceValues(program)) {
val choiceVal = choice.first
if(choiceVal==null) {
if(choice.first==null) {
// the else clause
translate(choice.second.statements)
} else {
val subtract = choiceVal-previousValue
previousValue = choiceVal
val choiceVal = choice.first!!.single()
val rval = RuntimeValue(conditionDt!!, choiceVal)
if (conditionDt in ByteDatatypes) {
prog.instr(Opcode.DUP_B)
prog.instr(Opcode.PUSH_BYTE, RuntimeValue(conditionDt!!, subtract))
prog.instr(opcodeCompare(conditionDt))
prog.instr(opcodeCompare(conditionDt), rval)
}
else {
prog.instr(Opcode.DUP_W)
prog.instr(Opcode.PUSH_WORD, RuntimeValue(conditionDt!!, subtract))
prog.instr(opcodeCompare(conditionDt))
prog.instr(opcodeCompare(conditionDt), rval)
}
val choiceLabel = makeLabel(whenstmt, "choice_$choiceVal")
choiceLabels.add(choiceLabel)
@ -2122,12 +2100,12 @@ internal class Compiler(private val program: Program) {
prog.instr(Opcode.JUMP, callLabel = endOfWhenLabel)
for(choice in whenstmt.choices.zip(choiceLabels)) {
// TODO the various code blocks here, don't forget to jump to the end label at their eind
prog.label(choice.second)
prog.instr(Opcode.NOP)
translate(choice.first.statements)
prog.instr(Opcode.JUMP, callLabel = endOfWhenLabel)
}
prog.removeLastInstruction() // remove the last jump, that can fall through to here
prog.label(endOfWhenLabel)
if (conditionDt in ByteDatatypes) prog.instr(Opcode.DISCARD_BYTE)

View File

@ -84,6 +84,7 @@ fun compileProgram(filepath: Path,
}
}
programAst.removeNops()
programAst.checkValid(compilerOptions) // check if final tree is valid
programAst.checkRecursion() // check if there are recursive subroutine calls

View File

@ -459,10 +459,14 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
fun writeCode(out: PrintStream, embeddedLabels: Boolean=true) {
out.println("; stackVM program code for '$name'")
out.println("%memory")
if(memory.isNotEmpty())
TODO("add support for writing/reading initial memory values")
out.println("%end_memory")
writeMemory(out)
writeHeap(out)
for(blk in blocks) {
writeBlock(out, blk, embeddedLabels)
}
}
private fun writeHeap(out: PrintStream) {
out.println("%heap")
heap.allEntries().forEach {
out.print("${it.key} ${it.value.type.name.toLowerCase()} ")
@ -491,34 +495,42 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
}
}
out.println("%end_heap")
for(blk in blocks) {
out.println("\n%block ${blk.name} ${blk.address?.toString(16) ?: ""}")
}
out.println("%variables")
for(variable in blk.variables) {
val valuestr = variable.value.toString()
out.println("${variable.key} ${variable.value.type.name.toLowerCase()} $valuestr")
}
out.println("%end_variables")
out.println("%memorypointers")
for(iconst in blk.memoryPointers) {
out.println("${iconst.key} ${iconst.value.second.name.toLowerCase()} uw:${iconst.value.first.toString(16)}")
}
out.println("%end_memorypointers")
out.println("%instructions")
val labels = blk.labels.entries.associateBy({it.value}) {it.key}
for(instr in blk.instructions) {
if(!embeddedLabels) {
val label = labels[instr]
if (label != null)
out.println("$label:")
} else {
out.println(instr)
}
}
out.println("%end_instructions")
private fun writeBlock(out: PrintStream, blk: ProgramBlock, embeddedLabels: Boolean) {
out.println("\n%block ${blk.name} ${blk.address?.toString(16) ?: ""}")
out.println("%end_block")
out.println("%variables")
for (variable in blk.variables) {
val valuestr = variable.value.toString()
out.println("${variable.key} ${variable.value.type.name.toLowerCase()} $valuestr")
}
out.println("%end_variables")
out.println("%memorypointers")
for (iconst in blk.memoryPointers) {
out.println("${iconst.key} ${iconst.value.second.name.toLowerCase()} uw:${iconst.value.first.toString(16)}")
}
out.println("%end_memorypointers")
out.println("%instructions")
val labels = blk.labels.entries.associateBy({ it.value }) { it.key }
for (instr in blk.instructions) {
if (!embeddedLabels) {
val label = labels[instr]
if (label != null)
out.println("$label:")
} else {
out.println(instr)
}
}
out.println("%end_instructions")
out.println("%end_block")
}
private fun writeMemory(out: PrintStream) {
out.println("%memory")
if (memory.isNotEmpty())
TODO("add support for writing/reading initial memory values")
out.println("%end_memory")
}
}

View File

@ -2,6 +2,10 @@ package prog8.compiler.target.c64
import prog8.compiler.toHex
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
fun optimizeAssembly(lines: MutableList<String>): Int {
var numberOfOptimizations = 0
@ -24,10 +28,19 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
numberOfOptimizations++
}
removeLines = optimizeCmpSequence(linesByFour)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
removeLines = optimizeStoreLoadSame(linesByFour)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
@ -36,6 +49,7 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
linesByFourteen = getLinesBy(lines, 14)
numberOfOptimizations++
}
@ -44,6 +58,27 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
return numberOfOptimizations
}
fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
// the when statement (on bytes) generates a sequence of:
// lda $ce01,x
// cmp #$20
// beq check_prog8_s72choice_32
// lda $ce01,x
// cmp #$21
// beq check_prog8_s73choice_33
// the repeated lda can be removed
val removeLines = mutableListOf<Int>()
for(lines in linesByFour) {
if(lines[0].value.trim()=="lda ${(ESTACK_LO+1).toHex()},x" &&
lines[1].value.trim().startsWith("cmp ") &&
lines[2].value.trim().startsWith("beq ") &&
lines[3].value.trim()=="lda ${(ESTACK_LO+1).toHex()},x") {
removeLines.add(lines[3].index) // remove the second lda
}
}
return removeLines
}
fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
// sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write
// this is a lot harder for word values because the instruction sequence varies.
@ -128,7 +163,7 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
}
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
// all lines (that aren't empty or comments) in sliding pairs of 2
// all lines (that aren't empty or comments) in sliding windows of certain size
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Int> {

View File

@ -5,6 +5,9 @@ import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.Opcode
import prog8.compiler.toHex
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
internal class AsmPattern(val sequence: List<Opcode>, val altSequence: List<Opcode>?=null, val asm: (List<Instruction>)->String?)
internal fun loadAFromIndexedByVar(idxVarInstr: Instruction, readArrayInstr: Instruction): String {
@ -2063,7 +2066,20 @@ internal val patterns = listOf<AsmPattern>(
AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.CMP_B), listOf(Opcode.PUSH_VAR_BYTE, Opcode.CMP_UB)) { segment ->
// this pattern is encountered as part of the loop bound condition in for loops (var + cmp + jz/jnz)
val cmpval = segment[1].arg!!.integerValue()
" lda ${segment[0].callLabel} | cmp #$cmpval "
when(segment[0].callLabel) {
"A" -> {
" cmp #$cmpval "
}
"X" -> {
" cpx #$cmpval "
}
"Y" -> {
" cpy #$cmpval "
}
else -> {
" lda ${segment[0].callLabel} | cmp #$cmpval "
}
}
},
AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.CMP_W), listOf(Opcode.PUSH_VAR_WORD, Opcode.CMP_UW)) { segment ->
// this pattern is encountered as part of the loop bound condition in for loops (var + cmp + jz/jnz)
@ -2289,6 +2305,24 @@ internal val patterns = listOf<AsmPattern>(
ldx ${C64Zeropage.SCRATCH_REG_X}
"""
} else null
},
AsmPattern(listOf(Opcode.DUP_W, Opcode.CMP_UW),
listOf(Opcode.DUP_W, Opcode.CMP_W)) { segment ->
"""
lda ${(ESTACK_HI+1).toHex()},x
cmp #>${segment[1].arg!!.integerValue().toHex()}
bne +
lda ${(ESTACK_LO+1).toHex()},x
cmp #<${segment[1].arg!!.integerValue().toHex()}
; bne + not necessary?
; lda #0 not necessary?
+
"""
},
AsmPattern(listOf(Opcode.DUP_B, Opcode.CMP_UB),
listOf(Opcode.DUP_B, Opcode.CMP_B)) { segment ->
""" lda ${(ESTACK_LO+1).toHex()},x | cmp #${segment[1].arg!!.integerValue().toHex()} """
}
)

View File

@ -1086,5 +1086,38 @@ class Petscii {
val decodeTable = if(lowercase) decodingScreencodeLowercase else decodingScreencodeUppercase
return screencode.map { decodeTable[it.toInt()] }.joinToString("")
}
fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Short {
val code = when {
petscii_code <= 0x1f -> petscii_code + 128
petscii_code <= 0x3f -> petscii_code.toInt()
petscii_code <= 0x5f -> petscii_code - 64
petscii_code <= 0x7f -> petscii_code - 32
petscii_code <= 0x9f -> petscii_code + 64
petscii_code <= 0xbf -> petscii_code - 64
petscii_code <= 0xfe -> petscii_code - 128
petscii_code == 255.toShort() -> 95
else -> throw CharConversionException("petscii code out of range")
}
if(inverseVideo)
return (code or 0x80).toShort()
return code.toShort()
}
fun scr2petscii(screencode: Short): Short {
val petscii = when {
screencode <= 0x1f -> screencode + 64
screencode <= 0x3f -> screencode.toInt()
screencode <= 0x5d -> screencode +123
screencode == 0x5e.toShort() -> 255
screencode == 0x5f.toShort() -> 223
screencode <= 0x7f -> screencode + 64
screencode <= 0xbf -> screencode - 128
screencode <= 0xfe -> screencode - 64
screencode == 255.toShort() -> 191
else -> throw CharConversionException("screencode out of range")
}
return petscii.toShort()
}
}
}

View File

@ -10,6 +10,9 @@ import prog8.vm.stackvm.Syscall
import prog8.vm.stackvm.syscallsForStackVm
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
private var breakpointCounter = 0
internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.ProgramBlock): String? {
@ -61,13 +64,32 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.RRESTOREX -> " sta ${C64Zeropage.SCRATCH_REG} | pla | tax | lda ${C64Zeropage.SCRATCH_REG}"
Opcode.DISCARD_BYTE -> " inx"
Opcode.DISCARD_WORD -> " inx"
Opcode.DISCARD_FLOAT -> " inx | inx | inx"
Opcode.DUP_B -> {
" dex | lda ${(ESTACK_LO+1).toHex()},x | sta ${ESTACK_LO.toHex()},x"
" lda ${(ESTACK_LO+1).toHex()},x | sta ${ESTACK_LO.toHex()},x | dex | ;DUP_B "
}
Opcode.DUP_W -> {
" dex | lda ${(ESTACK_LO+1).toHex()},x | sta ${ESTACK_LO.toHex()},x | lda ${(ESTACK_HI+1).toHex()},x | sta ${ESTACK_HI.toHex()},x "
" lda ${(ESTACK_LO+1).toHex()},x | sta ${ESTACK_LO.toHex()},x | lda ${(ESTACK_HI+1).toHex()},x | sta ${ESTACK_HI.toHex()},x | dex "
}
Opcode.DISCARD_FLOAT -> " inx | inx | inx"
Opcode.CMP_B, Opcode.CMP_UB -> {
" inx | lda ${ESTACK_LO.toHex()},x | cmp #${ins.arg!!.integerValue().toHex()} | ;CMP_B "
}
Opcode.CMP_W, Opcode.CMP_UW -> {
"""
inx
lda ${ESTACK_HI.toHex()},x
cmp #>${ins.arg!!.integerValue().toHex()}
bne +
lda ${ESTACK_LO.toHex()},x
cmp #<${ins.arg.integerValue().toHex()}
; bne + not necessary?
; lda #0 not necessary?
+
"""
}
Opcode.INLINE_ASSEMBLY -> "@inline@" + (ins.callLabel2 ?: "") // All of the inline assembly is stored in the calllabel2 property. the '@inline@' is a special marker to accept it.
Opcode.INCLUDE_FILE -> {
val offset = if(ins.arg==null) "" else ", ${ins.arg.integerValue()}"

View File

@ -127,8 +127,7 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, program
}
}
if(arglist is IdentifierReference) {
val dt = arglist.inferType(program)
return when(dt) {
return when(val dt = arglist.inferType(program)) {
in NumericDatatypes -> dt!!
in StringDatatypes -> dt!!
in ArrayDatatypes -> ArrayElementTypes.getValue(dt!!)
@ -145,8 +144,7 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, program
return when (function) {
"abs" -> {
val dt = args.single().inferType(program)
when(dt) {
when(val dt = args.single().inferType(program)) {
in ByteDatatypes -> DataType.UBYTE
in WordDatatypes -> DataType.UWORD
DataType.FLOAT -> DataType.FLOAT
@ -154,8 +152,7 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, program
}
}
"max", "min" -> {
val dt = datatypeFromIterableArg(args.single())
when(dt) {
when(val dt = datatypeFromIterableArg(args.single())) {
in NumericDatatypes -> dt
in StringDatatypes -> DataType.UBYTE
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
@ -279,8 +276,7 @@ private fun builtinAbs(args: List<IExpression>, position: Position, program: Pro
throw SyntaxError("abs requires one numeric argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val number = constval.asNumericValue
return when (number) {
return when (val number = constval.asNumericValue) {
is Int, is Byte, is Short -> numericLiteral(abs(number.toInt()), args[0].position)
is Double -> numericLiteral(abs(number.toDouble()), args[0].position)
else -> throw SyntaxError("abs requires one numeric argument", position)

View File

@ -104,7 +104,12 @@ class CallGraph(private val program: Program): IAstVisitor {
}
override fun visit(subroutine: Subroutine) {
if((subroutine.name=="start" && subroutine.definingScope().name=="main")
val alwaysKeepSubroutines = setOf(
Pair("main", "start"),
Pair("irq", "irq")
)
if(Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines
|| subroutine.name== initvarsSubName || subroutine.definingModule().isLibraryModule) {
// make sure the entrypoint is mentioned in the used symbols
addNodeAndParentScopes(subroutine)

View File

@ -28,7 +28,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
override fun visit(decl: VarDecl): IStatement {
// the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true) {
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
errors.add(ExpressionError("recursive var declaration", decl.position))
return decl
}
@ -656,7 +656,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
val lv = assignment.value as? LiteralValue
if(lv!=null) {
// see if we can promote/convert a literal value to the required datatype
when(assignment.singleTarget?.inferType(program, assignment)) {
when(assignment.target.inferType(program, assignment)) {
DataType.UWORD -> {
// we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535,
if(lv.type== DataType.UBYTE)

View File

@ -35,7 +35,7 @@ internal fun Program.optimizeStatements(optimizeInlining: Boolean): Int {
val namescope = scope.parent as INameScope
val idx = namescope.statements.indexOf(scope as IStatement)
if(idx>=0) {
namescope.statements[idx] = NopStatement(scope.position)
namescope.statements[idx] = NopStatement.insteadOf(namescope.statements[idx])
namescope.statements.addAll(idx, scope.statements)
}
}

View File

@ -36,21 +36,46 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
}
override fun visit(typecast: TypecastExpression): IExpression {
// remove redundant typecasts
var tc = typecast
// try to statically convert a literal value into one of the desired type
val literal = tc.expression as? LiteralValue
if(literal!=null) {
val newLiteral = literal.cast(tc.type)
if(newLiteral!=null && newLiteral!==literal) {
optimizationsDone++
return newLiteral
}
}
// remove redundant typecasts
while(true) {
val expr = tc.expression
if(expr !is TypecastExpression || expr.type!=tc.type) {
val assignment = typecast.parent as? Assignment
if(assignment!=null) {
val targetDt = assignment.singleTarget?.inferType(program, assignment)
val targetDt = assignment.target.inferType(program, assignment)
if(tc.expression.inferType(program)==targetDt) {
optimizationsDone++
return tc.expression
}
}
val subTc = tc.expression as? TypecastExpression
if(subTc!=null) {
// if the previous typecast was casting to a 'bigger' type, just ignore that one
// if the previous typecast was casting to a similar type, ignore that one
if(subTc.type largerThan tc.type || subTc.type equalsSize tc.type) {
subTc.type = tc.type
subTc.parent = tc.parent
optimizationsDone++
return subTc
}
}
return super.visit(tc)
}
optimizationsDone++
tc = expr
}
@ -385,7 +410,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
}
if(leftConstVal==null && rightConstVal!=null) {
if(leftDt biggerThan rightDt) {
if(leftDt largerThan rightDt) {
val (adjusted, newValue) = adjust(rightConstVal, leftDt)
if (adjusted) {
expr.right = newValue
@ -395,7 +420,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
}
return false
} else if(leftConstVal!=null && rightConstVal==null) {
if(rightDt biggerThan leftDt) {
if(rightDt largerThan leftDt) {
val (adjusted, newValue) = adjust(leftConstVal, rightDt)
if (adjusted) {
expr.left = newValue

View File

@ -4,6 +4,7 @@ import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
import prog8.compiler.target.c64.Petscii
import prog8.functions.BuiltinFunctions
@ -15,11 +16,11 @@ import kotlin.math.floor
todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to) + print warning about this
*/
internal class StatementOptimizer(private val program: Program, private val optimizeInlining: Boolean) : IAstModifyingVisitor {
var optimizationsDone: Int = 0
private set
var scopesToFlatten = mutableListOf<INameScope>()
val nopStatements = mutableListOf<NopStatement>()
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
private val callgraph = CallGraph(program)
@ -34,9 +35,6 @@ internal class StatementOptimizer(private val program: Program, private val opti
inlineSubroutines(callgraph)
}
super.visit(program)
// at the end, remove the encountered NOP statements
this.nopStatements.forEach { it.definingScope().remove(it) }
}
private fun inlineSubroutines(callgraph: CallGraph) {
@ -81,7 +79,6 @@ internal class StatementOptimizer(private val program: Program, private val opti
}
val returns = inlined.statements.withIndex().filter { iv -> iv.value is Return }.map { iv -> Pair(iv.index, iv.value as Return)}
for(returnIdx in returns) {
assert(returnIdx.second.values.isEmpty())
val jump = Jump(null, IdentifierReference(listOf(endlabel.name), returnIdx.second.position), null, returnIdx.second.position)
inlined.statements[returnIdx.first] = jump
endLabelUsed = true
@ -145,13 +142,13 @@ internal class StatementOptimizer(private val program: Program, private val opti
if (block.containsNoCodeNorVars()) {
optimizationsDone++
printWarning("removing empty block '${block.name}'", block.position)
return NopStatement(block.position)
return NopStatement.insteadOf(block)
}
if (block !in callgraph.usedSymbols) {
optimizationsDone++
printWarning("removing unused block '${block.name}'", block.position)
return NopStatement(block.position) // remove unused block
return NopStatement.insteadOf(block) // remove unused block
}
}
@ -165,7 +162,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(subroutine.containsNoCodeNorVars()) {
printWarning("removing empty subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++
return NopStatement(subroutine.position)
return NopStatement.insteadOf(subroutine)
}
}
@ -189,7 +186,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++
return NopStatement(subroutine.position) // remove unused subroutine
return NopStatement.insteadOf(subroutine)
}
return subroutine
@ -201,7 +198,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(decl.type!=VarDeclType.CONST)
printWarning("removing unused variable '${decl.name}'", decl.position)
optimizationsDone++
return NopStatement(decl.position) // remove unused variable
return NopStatement.insteadOf(decl)
}
return super.visit(decl)
@ -219,9 +216,9 @@ internal class StatementOptimizer(private val program: Program, private val opti
continue
} else {
val prev = statements[previousAssignmentLine] as Assignment
if (prev.targets.size == 1 && stmt.targets.size == 1 && prev.targets[0].isSameAs(stmt.targets[0], program)) {
if (prev.target.isSameAs(stmt.target, program)) {
// get rid of the previous assignment, if the target is not MEMORY
if (prev.targets[0].isNotMemory(program.namespace))
if (prev.target.isNotMemory(program.namespace))
linesToRemove.add(previousAssignmentLine)
}
previousAssignmentLine = i
@ -238,7 +235,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
if (functionName in pureBuiltinFunctions) {
printWarning("statement has no effect (function return value is discarded)", functionCallStatement.position)
optimizationsDone++
return NopStatement(functionCallStatement.position)
return NopStatement.insteadOf(functionCallStatement)
}
}
@ -283,7 +280,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
}
if(first is ReturnFromIrq || first is Return) {
optimizationsDone++
return NopStatement(functionCallStatement.position)
return NopStatement.insteadOf(functionCallStatement)
}
}
@ -301,8 +298,8 @@ internal class StatementOptimizer(private val program: Program, private val opti
optimizationsDone++
return FunctionCall(first.identifier, functionCall.arglist, functionCall.position)
}
if(first is Return && first.values.size==1) {
val constval = first.values[0].constValue(program)
if(first is Return && first.value!=null) {
val constval = first.value?.constValue(program)
if(constval!=null)
return constval
}
@ -315,7 +312,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars()) {
optimizationsDone++
return NopStatement(ifStatement.position)
return NopStatement.insteadOf(ifStatement)
}
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
@ -349,13 +346,13 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(forLoop.body.containsNoCodeNorVars()) {
// remove empty for loop
optimizationsDone++
return NopStatement(forLoop.position)
return NopStatement.insteadOf(forLoop)
} else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl
if(loopvar!=null && loopvar.name==forLoop.loopVar?.nameInSource?.singleOrNull()) {
// remove empty for loop
optimizationsDone++
return NopStatement(forLoop.position)
return NopStatement.insteadOf(forLoop)
}
}
@ -365,7 +362,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(range.size()==1) {
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val assignment = Assignment(listOf(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position)), null, range.from, forLoop.position)
val assignment = Assignment(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, range.from, forLoop.position)
forLoop.body.statements.add(0, assignment)
optimizationsDone++
return forLoop.body
@ -394,7 +391,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
// always false -> ditch whole statement
printWarning("condition is always false", whileLoop.position)
optimizationsDone++
NopStatement(whileLoop.position)
NopStatement.insteadOf(whileLoop)
}
}
return whileLoop
@ -430,11 +427,6 @@ internal class StatementOptimizer(private val program: Program, private val opti
return repeatLoop
}
override fun visit(nopStatement: NopStatement): IStatement {
this.nopStatements.add(nopStatement)
return nopStatement
}
override fun visit(whenStatement: WhenStatement): IStatement {
val choices = whenStatement.choices.toList()
for(choice in choices) {
@ -486,7 +478,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(label!=null) {
if(scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1) {
optimizationsDone++
return NopStatement(jump.position)
return NopStatement.insteadOf(jump)
}
}
@ -497,125 +489,122 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(assignment.aug_op!=null)
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
if(assignment.targets.size==1) {
val target=assignment.targets[0]
if(target isSameAs assignment.value) {
optimizationsDone++
return NopStatement(assignment.position)
}
val targetDt = target.inferType(program, assignment)
val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) {
val cv = bexpr.right.constValue(program)?.asNumericValue?.toDouble()
if(cv==null) {
if(bexpr.operator=="+" && targetDt!= DataType.FLOAT) {
if (bexpr.left isSameAs bexpr.right && target isSameAs bexpr.left) {
bexpr.operator = "*"
bexpr.right = LiteralValue.optimalInteger(2, assignment.value.position)
optimizationsDone++
return assignment
}
if(assignment.target isSameAs assignment.value) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
val targetDt = assignment.target.inferType(program, assignment)
val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) {
val cv = bexpr.right.constValue(program)?.asNumericValue?.toDouble()
if (cv == null) {
if (bexpr.operator == "+" && targetDt != DataType.FLOAT) {
if (bexpr.left isSameAs bexpr.right && assignment.target isSameAs bexpr.left) {
bexpr.operator = "*"
bexpr.right = LiteralValue.optimalInteger(2, assignment.value.position)
optimizationsDone++
return assignment
}
} else {
if (target isSameAs bexpr.left) {
// remove assignments that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc
// A = A <operator> B
val vardeclDt = (target.identifier?.targetVarDecl(program.namespace))?.type
}
} else {
if (assignment.target isSameAs bexpr.left) {
// remove assignments that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc
// A = A <operator> B
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
when (bexpr.operator) {
"+" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement(assignment.position)
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt!=VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several INCs (a bit less when dealing with memory targets)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
decs.statements.add(PostIncrDecr(target, "++", assignment.position))
}
return decs
when (bexpr.operator) {
"+" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several INCs (a bit less when dealing with memory targets)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
decs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
}
return decs
}
}
"-" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement(assignment.position)
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt!=VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several DECs (a bit less when dealing with memory targets)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
decs.statements.add(PostIncrDecr(target, "--", assignment.position))
}
return decs
}
"-" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several DECs (a bit less when dealing with memory targets)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
}
return decs
}
}
"*" -> if (cv == 1.0) {
}
"*" -> if (cv == 1.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"/" -> if (cv == 1.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"**" -> if (cv == 1.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"|" -> if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"^" -> if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"<<" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement(assignment.position)
return NopStatement.insteadOf(assignment)
}
"/" -> if (cv == 1.0) {
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) ||
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
assignment.value = LiteralValue.optimalInteger(0, assignment.value.position)
assignment.value.linkParents(assignment)
optimizationsDone++
return NopStatement(assignment.position)
}
"**" -> if (cv == 1.0) {
optimizationsDone++
return NopStatement(assignment.position)
}
"|" -> if (cv == 0.0) {
optimizationsDone++
return NopStatement(assignment.position)
}
"^" -> if (cv == 0.0) {
optimizationsDone++
return NopStatement(assignment.position)
}
"<<" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement(assignment.position)
}
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) ||
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
assignment.value = LiteralValue.optimalInteger(0, assignment.value.position)
assignment.value.linkParents(assignment)
optimizationsDone++
} else {
// replace by in-place lsl(...) call
val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt()
while (numshifts > 0) {
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsl"), assignment.position), mutableListOf(bexpr.left), assignment.position))
numshifts--
}
optimizationsDone++
return scope
} else {
// replace by in-place lsl(...) call
val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt()
while (numshifts > 0) {
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsl"), assignment.position), mutableListOf(bexpr.left), assignment.position))
numshifts--
}
optimizationsDone++
return scope
}
">>" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement(assignment.position)
}
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) ||
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
assignment.value = LiteralValue.optimalInteger(0, assignment.value.position)
assignment.value.linkParents(assignment)
optimizationsDone++
} else {
// replace by in-place lsr(...) call
val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt()
while (numshifts > 0) {
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsr"), assignment.position), mutableListOf(bexpr.left), assignment.position))
numshifts--
}
optimizationsDone++
return scope
}
">>" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) ||
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
assignment.value = LiteralValue.optimalInteger(0, assignment.value.position)
assignment.value.linkParents(assignment)
optimizationsDone++
} else {
// replace by in-place lsr(...) call
val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt()
while (numshifts > 0) {
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsr"), assignment.position), mutableListOf(bexpr.left), assignment.position))
numshifts--
}
optimizationsDone++
return scope
}
}
}
@ -623,6 +612,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
}
}
return super.visit(assignment)
}
@ -644,7 +634,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
val stmts = label.definingScope().statements
val startIdx = stmts.indexOf(label)
if(startIdx<(stmts.size-1) && stmts[startIdx+1] == label)
return NopStatement(label.position)
return NopStatement.insteadOf(label)
return super.visit(label)
}
@ -652,3 +642,18 @@ internal class StatementOptimizer(private val program: Program, private val opti
internal class RemoveNops: IAstVisitor {
val nopStatements = mutableListOf<NopStatement>()
override fun visit(program: Program) {
super.visit(program)
// at the end, remove the encountered NOP statements
this.nopStatements.forEach {
it.definingScope().remove(it)
}
}
override fun visit(nopStatement: NopStatement) {
nopStatements.add(nopStatement)
}
}

View File

@ -26,7 +26,7 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
in NumericDatatypes -> RuntimeValue(literalValue.type, num = literalValue.asNumericValue!!)
in StringDatatypes -> from(literalValue.heapId!!, heap)
in ArrayDatatypes -> from(literalValue.heapId!!, heap)
else -> TODO("type")
else -> throw IllegalArgumentException("weird source value $literalValue")
}
}
@ -40,11 +40,19 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
RuntimeValue(value.type, array = value.doubleArray!!.toList().toTypedArray(), heapId = heapId)
} else {
val array = value.array!!
if (array.any { it.addressOf != null })
TODO("addressof values")
RuntimeValue(value.type, array = array.map { it.integer!! }.toTypedArray(), heapId = heapId)
val resultArray = mutableListOf<Number>()
for(elt in array.withIndex()){
if(elt.value.integer!=null)
resultArray.add(elt.value.integer!!)
else {
println("ADDRESSOF ${elt.value}")
resultArray.add(0x8000)
}
}
RuntimeValue(value.type, array = resultArray.toTypedArray(), heapId = heapId)
//RuntimeValue(value.type, array = array.map { it.integer!! }.toTypedArray(), heapId = heapId)
}
else -> TODO("weird type on heap")
else -> throw IllegalArgumentException("weird value type on heap $value")
}
}
@ -53,27 +61,37 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
init {
when(type) {
DataType.UBYTE -> {
byteval = (num!!.toInt() and 255).toShort()
val inum = num!!.toInt()
if(inum !in 0 .. 255)
throw IllegalArgumentException("invalid value for ubyte: $inum")
byteval = inum.toShort()
wordval = null
floatval = null
asBoolean = byteval != 0.toShort()
}
DataType.BYTE -> {
val v = num!!.toInt() and 255
byteval = (if(v<128) v else v-256).toShort()
val inum = num!!.toInt()
if(inum !in -128 .. 127)
throw IllegalArgumentException("invalid value for byte: $inum")
byteval = inum.toShort()
wordval = null
floatval = null
asBoolean = byteval != 0.toShort()
}
DataType.UWORD -> {
wordval = num!!.toInt() and 65535
val inum = num!!.toInt()
if(inum !in 0 .. 65535)
throw IllegalArgumentException("invalid value for uword: $inum")
wordval = inum
byteval = null
floatval = null
asBoolean = wordval != 0
}
DataType.WORD -> {
val v = num!!.toInt() and 65535
wordval = if(v<32768) v else v - 65536
val inum = num!!.toInt()
if(inum !in -32768 .. 32767)
throw IllegalArgumentException("invalid value for word: $inum")
wordval = inum
byteval = null
floatval = null
asBoolean = wordval != 0
@ -104,7 +122,7 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
in ArrayDatatypes -> LiteralValue(type,
arrayvalue = array?.map { LiteralValue.optimalNumeric(it, Position("", 0, 0, 0)) }?.toTypedArray(),
position = Position("", 0, 0, 0))
else -> TODO("strange type")
else -> throw IllegalArgumentException("weird source value $this")
}
}
@ -179,20 +197,44 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
if(leftDt== DataType.UBYTE)
RuntimeValue(DataType.UBYTE, (number xor 255) + 1)
else
RuntimeValue(DataType.UBYTE, (number xor 65535) + 1)
RuntimeValue(DataType.UWORD, (number xor 65535) + 1)
}
DataType.BYTE -> {
val v=result.toInt() and 255
if(v<128)
RuntimeValue(DataType.BYTE, v)
else
RuntimeValue(DataType.BYTE, v-256)
}
DataType.WORD -> {
val v=result.toInt() and 65535
if(v<32768)
RuntimeValue(DataType.WORD, v)
else
RuntimeValue(DataType.WORD, v-65536)
}
DataType.BYTE -> RuntimeValue(DataType.BYTE, result.toInt())
DataType.WORD -> RuntimeValue(DataType.WORD, result.toInt())
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result)
else -> throw ArithmeticException("$op on non-numeric type")
}
}
return when(leftDt) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, result.toInt())
DataType.BYTE -> RuntimeValue(DataType.BYTE, result.toInt())
DataType.UWORD -> RuntimeValue(DataType.UWORD, result.toInt())
DataType.WORD -> RuntimeValue(DataType.WORD, result.toInt())
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, result.toInt() and 255)
DataType.BYTE -> {
val v = result.toInt() and 255
if(v<128)
RuntimeValue(DataType.BYTE, v)
else
RuntimeValue(DataType.BYTE, v-256)
}
DataType.UWORD -> RuntimeValue(DataType.UWORD, result.toInt() and 65535)
DataType.WORD -> {
val v = result.toInt() and 65535
if(v<32768)
RuntimeValue(DataType.WORD, v)
else
RuntimeValue(DataType.WORD, v-65536)
}
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result)
else -> throw ArithmeticException("$op on non-numeric type")
}
@ -268,10 +310,22 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
fun shl(): RuntimeValue {
val v = integerValue()
return when (type) {
DataType.UBYTE,
DataType.BYTE,
DataType.UWORD,
DataType.WORD -> RuntimeValue(type, v shl 1)
DataType.UBYTE -> RuntimeValue(type, (v shl 1) and 255)
DataType.UWORD -> RuntimeValue(type, (v shl 1) and 65535)
DataType.BYTE -> {
val value = v shl 1
if(value<128)
RuntimeValue(type, value)
else
RuntimeValue(type, value-256)
}
DataType.WORD -> {
val value = v shl 1
if(value<32768)
RuntimeValue(type, value)
else
RuntimeValue(type, value-65536)
}
else -> throw ArithmeticException("invalid type for shl: $type")
}
}
@ -409,16 +463,32 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
fun inv(): RuntimeValue {
return when(type) {
in ByteDatatypes -> RuntimeValue(type, byteval!!.toInt().inv())
in WordDatatypes -> RuntimeValue(type, wordval!!.inv())
DataType.UBYTE -> RuntimeValue(type, byteval!!.toInt().inv() and 255)
DataType.UWORD -> RuntimeValue(type, wordval!!.inv() and 65535)
DataType.BYTE -> RuntimeValue(type, byteval!!.toInt().inv())
DataType.WORD -> RuntimeValue(type, wordval!!.inv())
else -> throw ArithmeticException("inv can only work on byte/word")
}
}
fun inc(): RuntimeValue {
return when(type) {
in ByteDatatypes -> RuntimeValue(type, byteval!! + 1)
in WordDatatypes -> RuntimeValue(type, wordval!! + 1)
DataType.UBYTE -> RuntimeValue(type, (byteval!! + 1) and 255)
DataType.UWORD -> RuntimeValue(type, (wordval!! + 1) and 65535)
DataType.BYTE -> {
val newval = byteval!! + 1
if(newval == 128)
RuntimeValue(type, -128)
else
RuntimeValue(type, newval)
}
DataType.WORD -> {
val newval = wordval!! + 1
if(newval == 32768)
RuntimeValue(type, -32768)
else
RuntimeValue(type, newval)
}
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, floatval!! + 1)
else -> throw ArithmeticException("inc can only work on numeric types")
}
@ -426,8 +496,22 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
fun dec(): RuntimeValue {
return when(type) {
in ByteDatatypes -> RuntimeValue(type, byteval!! - 1)
in WordDatatypes -> RuntimeValue(type, wordval!! - 1)
DataType.UBYTE -> RuntimeValue(type, (byteval!! - 1) and 255)
DataType.UWORD -> RuntimeValue(type, (wordval!! - 1) and 65535)
DataType.BYTE -> {
val newval = byteval!! - 1
if(newval == -129)
RuntimeValue(type, 127)
else
RuntimeValue(type, newval)
}
DataType.WORD -> {
val newval = wordval!! - 1
if(newval == -32769)
RuntimeValue(type, 32767)
else
RuntimeValue(type, newval)
}
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, floatval!! - 1)
else -> throw ArithmeticException("dec can only work on numeric types")
}
@ -446,9 +530,21 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
DataType.UBYTE -> {
when (targetType) {
DataType.UBYTE -> this
DataType.BYTE -> RuntimeValue(DataType.BYTE, byteval)
DataType.BYTE -> {
val nval=byteval!!.toInt()
if(nval<128)
RuntimeValue(DataType.BYTE, nval)
else
RuntimeValue(DataType.BYTE, nval-256)
}
DataType.UWORD -> RuntimeValue(DataType.UWORD, numericValue())
DataType.WORD -> RuntimeValue(DataType.WORD, numericValue())
DataType.WORD -> {
val nval = numericValue().toInt()
if(nval<32768)
RuntimeValue(DataType.WORD, nval)
else
RuntimeValue(DataType.WORD, nval-65536)
}
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
@ -456,8 +552,8 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
DataType.BYTE -> {
when (targetType) {
DataType.BYTE -> this
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue())
DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue())
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue() and 65535)
DataType.WORD -> RuntimeValue(DataType.WORD, integerValue())
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
@ -465,18 +561,36 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
}
DataType.UWORD -> {
when (targetType) {
DataType.BYTE -> RuntimeValue(DataType.BYTE, integerValue())
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue())
DataType.BYTE -> {
val v=integerValue()
if(v<128)
RuntimeValue(DataType.BYTE, v)
else
RuntimeValue(DataType.BYTE, v-256)
}
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> this
DataType.WORD -> RuntimeValue(DataType.WORD, integerValue())
DataType.WORD -> {
val v=integerValue()
if(v<32768)
RuntimeValue(DataType.WORD, v)
else
RuntimeValue(DataType.WORD, v-65536)
}
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.WORD -> {
when (targetType) {
DataType.BYTE -> RuntimeValue(DataType.BYTE, integerValue())
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue())
DataType.BYTE -> {
val v = integerValue() and 255
if(v<128)
RuntimeValue(DataType.BYTE, v)
else
RuntimeValue(DataType.BYTE, v-256)
}
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 65535)
DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue())
DataType.WORD -> this
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())

View File

@ -6,13 +6,17 @@ import prog8.ast.base.initvarsSubName
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.LiteralValue
import prog8.ast.statements.*
import prog8.compiler.target.c64.Mflpt5
import prog8.vm.RuntimeValue
import prog8.vm.RuntimeValueRange
import prog8.compiler.target.c64.Petscii
import java.awt.EventQueue
import java.io.CharConversionException
import java.util.*
import kotlin.NoSuchElementException
import kotlin.concurrent.fixedRateTimer
import kotlin.math.min
import kotlin.math.*
import kotlin.random.Random
class VmExecutionException(msg: String?) : Exception(msg)
@ -94,14 +98,12 @@ class RuntimeVariables {
fun get(scope: INameScope, name: String): RuntimeValue {
val where = vars.getValue(scope)
val value = where[name] ?: throw NoSuchElementException("no such runtime variable: ${scope.name}.$name")
return value
return where[name] ?: throw NoSuchElementException("no such runtime variable: ${scope.name}.$name")
}
fun getMemoryAddress(scope: INameScope, name: String): Int {
val where = memvars.getValue(scope)
val address = where[name] ?: throw NoSuchElementException("no such runtime memory-variable: ${scope.name}.$name")
return address
return where[name] ?: throw NoSuchElementException("no such runtime memory-variable: ${scope.name}.$name")
}
fun swap(a1: VarDecl, a2: VarDecl) = swap(a1.definingScope(), a1.name, a2.definingScope(), a2.name)
@ -128,9 +130,18 @@ class AstVm(val program: Program) {
val bootTime = System.currentTimeMillis()
var rtcOffset = bootTime
private val rnd = Random(0)
private val statusFlagsSave = Stack<StatusFlags>()
private val registerXsave = Stack<RuntimeValue>()
private val registerYsave = Stack<RuntimeValue>()
private val registerAsave = Stack<RuntimeValue>()
init {
// observe the jiffyclock
// observe the jiffyclock and screen matrix
mem.observe(0xa0, 0xa1, 0xa2)
for(i in 1024..2023)
mem.observe(i)
dialog.requestFocusInWindow()
@ -159,6 +170,11 @@ class AstVm(val program: Program) {
val jiffies = (time_hi.toInt() shl 16) + (time_mid.toInt() shl 8) + time_lo
rtcOffset = bootTime - (jiffies*1000/60)
}
if(address in 1024..2023) {
// write to the screen matrix
val scraddr = address-1024
dialog.canvas.setChar(scraddr % 40, scraddr / 40, value, 1)
}
return value
}
@ -213,6 +229,7 @@ class AstVm(val program: Program) {
}
}
}
dialog.canvas.printText("\n<program ended>", true)
println("PROGRAM EXITED!")
dialog.title = "PROGRAM EXITED"
} catch (tx: VmTerminationException) {
@ -228,7 +245,11 @@ class AstVm(val program: Program) {
if(statusflags.irqd)
return // interrupt is disabled
val jiffies = min((timeStamp-rtcOffset)*60/1000, 24*3600*60-1)
var jiffies = (timeStamp-rtcOffset)*60/1000
if(jiffies>24*3600*60-1) {
jiffies = 0
rtcOffset = timeStamp
}
// update the C-64 60hz jiffy clock in the ZP addresses:
mem.setUByte_DMA(0x00a0, (jiffies ushr 16).toShort())
mem.setUByte_DMA(0x00a1, (jiffies ushr 8 and 255).toShort())
@ -236,17 +257,19 @@ class AstVm(val program: Program) {
}
private val runtimeVariables = RuntimeVariables()
private val functions = BuiltinFunctions()
private val evalCtx = EvalContext(program, mem, statusflags, runtimeVariables, functions, ::executeSubroutine)
private val evalCtx = EvalContext(program, mem, statusflags, runtimeVariables, ::performBuiltinFunction, ::executeSubroutine)
class LoopControlBreak : Exception()
class LoopControlContinue : Exception()
class LoopControlReturn(val returnvalues: List<RuntimeValue>) : Exception()
class LoopControlReturn(val returnvalue: RuntimeValue?) : Exception()
class LoopControlJump(val identifier: IdentifierReference?, val address: Int?, val generatedLabel: String?) : Exception()
internal fun executeSubroutine(sub: Subroutine, arguments: List<RuntimeValue>, startlabel: Label?=null): List<RuntimeValue> {
assert(!sub.isAsmSubroutine)
internal fun executeSubroutine(sub: Subroutine, arguments: List<RuntimeValue>, startlabel: Label?=null): RuntimeValue? {
if(sub.isAsmSubroutine) {
return performSyscall(sub, arguments)
}
if (sub.statements.isEmpty())
throw VmTerminationException("scope contains no statements: $sub")
if (arguments.size != sub.parameters.size)
@ -277,7 +300,7 @@ class AstVm(val program: Program) {
}
}
} catch (r: LoopControlReturn) {
return r.returnvalues
return r.returnvalue
}
throw VmTerminationException("instruction pointer overflow, is a return missing? $sub")
}
@ -324,7 +347,7 @@ class AstVm(val program: Program) {
executeSwap(stmt)
} else {
val args = evaluate(stmt.arglist)
functions.performBuiltinFunction(target.name, args, statusflags)
performBuiltinFunction(target.name, args, statusflags)
}
}
else -> {
@ -332,37 +355,81 @@ class AstVm(val program: Program) {
}
}
}
is Return -> throw LoopControlReturn(stmt.values.map { evaluate(it, evalCtx) })
is Return -> {
val value =
if(stmt.value==null)
null
else
evaluate(stmt.value!!, evalCtx)
throw LoopControlReturn(value)
}
is Continue -> throw LoopControlContinue()
is Break -> throw LoopControlBreak()
is Assignment -> {
if (stmt.aug_op != null)
throw VmExecutionException("augmented assignment should have been converted into regular one $stmt")
val target = stmt.singleTarget
if (target != null) {
val value = evaluate(stmt.value, evalCtx)
performAssignment(target, value, stmt, evalCtx)
} else TODO("assign multitarget $stmt")
val value = evaluate(stmt.value, evalCtx)
performAssignment(stmt.target, value, stmt, evalCtx)
}
is PostIncrDecr -> {
when {
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)
when(ident.type){
VarDeclType.VAR -> {
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))
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
runtimeVariables.set(identScope, ident.name, value)
}
VarDeclType.MEMORY -> {
val addr=ident.value!!.constValue(program)!!.asIntegerValue!!
val newval = when {
stmt.operator == "++" -> mem.getUByte(addr)+1 and 255
stmt.operator == "--" -> mem.getUByte(addr)-1 and 255
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
mem.setUByte(addr,newval.toShort())
}
VarDeclType.CONST -> throw VmExecutionException("can't be const")
}
}
stmt.target.memoryAddress != null -> {
val addr = evaluate(stmt.target.memoryAddress!!.addressExpression, evalCtx).integerValue()
val newval = when {
stmt.operator == "++" -> mem.getUByte(addr)+1 and 255
stmt.operator == "--" -> mem.getUByte(addr)-1 and 255
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
mem.setUByte(addr,newval.toShort())
}
stmt.target.arrayindexed != null -> {
val arrayvar = stmt.target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!
val arrayvalue = runtimeVariables.get(arrayvar.definingScope(), arrayvar.name)
val elementType = stmt.target.arrayindexed!!.inferType(program)!!
val index = evaluate(stmt.target.arrayindexed!!.arrayspec.index, evalCtx).integerValue()
var value = RuntimeValue(elementType, arrayvalue.array!![index].toInt())
when {
stmt.operator == "++" -> value=value.inc()
stmt.operator == "--" -> value=value.dec()
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
arrayvalue.array[index] = value.numericValue()
}
stmt.target.register != null -> {
var value = runtimeVariables.get(program.namespace, stmt.target.register!!.name)
value = when {
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 -> {
TODO("postincrdecr memory $stmt")
}
stmt.target.arrayindexed != null -> {
TODO("postincrdecr array $stmt")
runtimeVariables.set(program.namespace, stmt.target.register!!.name, value)
}
else -> throw VmExecutionException("empty postincrdecr? $stmt")
}
}
is Jump -> throw LoopControlJump(stmt.identifier, stmt.address, stmt.generatedLabel)
@ -370,7 +437,7 @@ class AstVm(val program: Program) {
if (sub is Subroutine) {
val args = sub.parameters.map { runtimeVariables.get(sub, it.name) }
performSyscall(sub, args)
throw LoopControlReturn(emptyList())
throw LoopControlReturn(null)
}
throw VmExecutionException("can't execute inline assembly in $sub")
}
@ -445,12 +512,12 @@ class AstVm(val program: Program) {
is WhenStatement -> {
val condition=evaluate(stmt.condition, evalCtx)
for(choice in stmt.choices) {
if(choice.value==null) {
if(choice.values==null) {
// the 'else' choice
executeAnonymousScope(choice.statements)
break
} else {
val value = choice.value.constValue(evalCtx.program) ?: throw VmExecutionException("can only use const values in when choices ${choice.position}")
val value = choice.values.single().constValue(evalCtx.program) ?: throw VmExecutionException("can only use const values in when choices ${choice.position}")
val rtval = RuntimeValue.from(value, evalCtx.program.heap)
if(condition==rtval) {
executeAnonymousScope(choice.statements)
@ -491,7 +558,7 @@ class AstVm(val program: Program) {
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 -> throw VmExecutionException("weird memaddress type $decl")
}
} else
runtimeVariables.set(decl.definingScope(), decl.name, value)
@ -501,46 +568,62 @@ class AstVm(val program: Program) {
evalCtx.mem.setUByte(address, value.byteval!!)
}
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")
val vardecl = target.arrayindexed.identifier.targetVarDecl(program.namespace)!!
if(vardecl.type==VarDeclType.VAR) {
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}")
}
DataType.ARRAY_B -> {
if (value.type != DataType.BYTE)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
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))
}
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 = 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))
else {
val address = (vardecl.value as LiteralValue).asIntegerValue!!
val index = evaluate(target.arrayindexed.arrayspec.index, evalCtx).integerValue()
val elementType = target.arrayindexed.inferType(program)!!
when(elementType) {
DataType.UBYTE -> mem.setUByte(address+index, value.byteval!!)
DataType.BYTE -> mem.setSByte(address+index, value.byteval!!)
DataType.UWORD -> mem.setUWord(address+index*2, value.wordval!!)
DataType.WORD -> mem.setSWord(address+index*2, value.wordval!!)
DataType.FLOAT -> mem.setFloat(address+index*Mflpt5.MemorySize, value.floatval!!)
else -> throw VmExecutionException("strange array elt type $elementType")
}
}
}
target.register != null -> {
@ -559,54 +642,54 @@ class AstVm(val program: Program) {
private fun evaluate(args: List<IExpression>) = args.map { evaluate(it, evalCtx) }
private fun performSyscall(sub: Subroutine, args: List<RuntimeValue>) {
assert(sub.isAsmSubroutine)
private fun performSyscall(sub: Subroutine, args: List<RuntimeValue>): RuntimeValue? {
var result: RuntimeValue? = null
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) {
val str = program.heap.get(args[0].wordval!!).str!!
dialog.canvas.printText(str, 1, true)
dialog.canvas.printText(str, true)
} else
dialog.canvas.printText(args[0].str!!, 1, true)
dialog.canvas.printText(args[0].str!!, true)
}
"c64scr.print_ub" -> {
dialog.canvas.printText(args[0].byteval!!.toString(), 1, true)
dialog.canvas.printText(args[0].byteval!!.toString(), true)
}
"c64scr.print_ub0" -> {
dialog.canvas.printText("%03d".format(args[0].byteval!!), 1, true)
dialog.canvas.printText("%03d".format(args[0].byteval!!), true)
}
"c64scr.print_b" -> {
dialog.canvas.printText(args[0].byteval!!.toString(), 1, true)
dialog.canvas.printText(args[0].byteval!!.toString(), true)
}
"c64scr.print_uw" -> {
dialog.canvas.printText(args[0].wordval!!.toString(), 1, true)
dialog.canvas.printText(args[0].wordval!!.toString(), true)
}
"c64scr.print_uw0" -> {
dialog.canvas.printText("%05d".format(args[0].wordval!!), 1, true)
dialog.canvas.printText("%05d".format(args[0].wordval!!), true)
}
"c64scr.print_w" -> {
dialog.canvas.printText(args[0].wordval!!.toString(), 1, true)
dialog.canvas.printText(args[0].wordval!!.toString(), true)
}
"c64scr.print_ubhex" -> {
val prefix = if (args[0].asBoolean) "$" else ""
val number = args[1].byteval!!
dialog.canvas.printText("$prefix${number.toString(16).padStart(2, '0')}", 1, true)
dialog.canvas.printText("$prefix${number.toString(16).padStart(2, '0')}", true)
}
"c64scr.print_uwhex" -> {
val prefix = if (args[0].asBoolean) "$" else ""
val number = args[1].wordval!!
dialog.canvas.printText("$prefix${number.toString(16).padStart(4, '0')}", 1, true)
dialog.canvas.printText("$prefix${number.toString(16).padStart(4, '0')}", true)
}
"c64scr.print_uwbin" -> {
val prefix = if (args[0].asBoolean) "%" else ""
val number = args[1].wordval!!
dialog.canvas.printText("$prefix${number.toString(2).padStart(16, '0')}", 1, true)
dialog.canvas.printText("$prefix${number.toString(2).padStart(16, '0')}", true)
}
"c64scr.print_ubbin" -> {
val prefix = if (args[0].asBoolean) "%" else ""
val number = args[1].byteval!!
dialog.canvas.printText("$prefix${number.toString(2).padStart(8, '0')}", 1, true)
dialog.canvas.printText("$prefix${number.toString(2).padStart(8, '0')}", true)
}
"c64scr.clear_screenchars" -> {
dialog.canvas.clearScreen(6)
@ -620,14 +703,252 @@ class AstVm(val program: Program) {
"c64scr.plot" -> {
dialog.canvas.setCursorPos(args[0].integerValue(), args[1].integerValue())
}
"c64.CHROUT" -> {
dialog.canvas.printChar(args[0].byteval!!)
"c64scr.input_chars" -> {
val input=mutableListOf<Char>()
for(i in 0 until 80) {
while(dialog.keyboardBuffer.isEmpty()) {
Thread.sleep(10)
}
val char=dialog.keyboardBuffer.pop()
if(char=='\n')
break
else {
input.add(char)
val printChar = try {
Petscii.encodePetscii("" + char, true).first()
} catch (cv: CharConversionException) {
0x3f.toShort()
}
dialog.canvas.printPetscii(printChar)
}
}
val inputStr = input.joinToString("")
val heapId = args[0].wordval!!
val origStr = program.heap.get(heapId).str!!
val paddedStr=inputStr.padEnd(origStr.length+1, '\u0000').substring(0, origStr.length)
program.heap.update(heapId, paddedStr)
result = RuntimeValue(DataType.UBYTE, paddedStr.indexOf('\u0000'))
}
"c64flt.print_f" -> {
dialog.canvas.printText(args[0].floatval.toString(), 1, true)
dialog.canvas.printText(args[0].floatval.toString(), true)
}
"c64.CHROUT" -> {
dialog.canvas.printPetscii(args[0].byteval!!)
}
"c64.CLEARSCR" -> {
dialog.canvas.clearScreen(6)
}
"c64.CHRIN" -> {
while(dialog.keyboardBuffer.isEmpty()) {
Thread.sleep(10)
}
val char=dialog.keyboardBuffer.pop()
result = RuntimeValue(DataType.UBYTE, char.toShort())
}
"c64utils.str2uword" -> {
val heapId = args[0].wordval!!
val argString = program.heap.get(heapId).str!!
val numericpart = argString.takeWhile { it.isDigit() }
result = RuntimeValue(DataType.UWORD, numericpart.toInt() and 65535)
}
else -> TODO("syscall ${sub.scopedname} $sub")
}
return result
}
private fun performBuiltinFunction(name: String, args: List<RuntimeValue>, statusflags: StatusFlags): RuntimeValue? {
return when (name) {
"rnd" -> RuntimeValue(DataType.UBYTE, rnd.nextInt() and 255)
"rndw" -> RuntimeValue(DataType.UWORD, rnd.nextInt() and 65535)
"rndf" -> RuntimeValue(DataType.FLOAT, rnd.nextDouble())
"lsb" -> RuntimeValue(DataType.UBYTE, args[0].integerValue() and 255)
"msb" -> RuntimeValue(DataType.UBYTE, (args[0].integerValue() ushr 8) and 255)
"sin" -> RuntimeValue(DataType.FLOAT, sin(args[0].numericValue().toDouble()))
"sin8" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (127.0 * sin(rad)).toShort())
}
"sin8u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort())
}
"sin16" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (32767.0 * sin(rad)).toShort())
}
"sin16u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (32768.0 + 32767.5 * sin(rad)).toShort())
}
"cos" -> RuntimeValue(DataType.FLOAT, cos(args[0].numericValue().toDouble()))
"cos8" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (127.0 * cos(rad)).toShort())
}
"cos8u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toShort())
}
"cos16" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (32767.0 * cos(rad)).toShort())
}
"cos16u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (32768.0 + 32767.5 * cos(rad)).toShort())
}
"tan" -> RuntimeValue(DataType.FLOAT, tan(args[0].numericValue().toDouble()))
"atan" -> RuntimeValue(DataType.FLOAT, atan(args[0].numericValue().toDouble()))
"ln" -> RuntimeValue(DataType.FLOAT, ln(args[0].numericValue().toDouble()))
"log2" -> RuntimeValue(DataType.FLOAT, log2(args[0].numericValue().toDouble()))
"sqrt" -> RuntimeValue(DataType.FLOAT, sqrt(args[0].numericValue().toDouble()))
"sqrt16" -> RuntimeValue(DataType.UBYTE, sqrt(args[0].wordval!!.toDouble()).toInt())
"rad" -> RuntimeValue(DataType.FLOAT, Math.toRadians(args[0].numericValue().toDouble()))
"deg" -> RuntimeValue(DataType.FLOAT, Math.toDegrees(args[0].numericValue().toDouble()))
"round" -> RuntimeValue(DataType.FLOAT, round(args[0].numericValue().toDouble()))
"floor" -> RuntimeValue(DataType.FLOAT, floor(args[0].numericValue().toDouble()))
"ceil" -> RuntimeValue(DataType.FLOAT, ceil(args[0].numericValue().toDouble()))
"rol" -> {
val (result, newCarry) = args[0].rol(statusflags.carry)
statusflags.carry = newCarry
return result
}
"rol2" -> args[0].rol2()
"ror" -> {
val (result, newCarry) = args[0].ror(statusflags.carry)
statusflags.carry = newCarry
return result
}
"ror2" -> args[0].ror2()
"lsl" -> args[0].shl()
"lsr" -> args[0].shr()
"abs" -> {
when (args[0].type) {
DataType.UBYTE -> args[0]
DataType.BYTE -> RuntimeValue(DataType.UBYTE, abs(args[0].numericValue().toDouble()))
DataType.UWORD -> args[0]
DataType.WORD -> RuntimeValue(DataType.UWORD, abs(args[0].numericValue().toDouble()))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, abs(args[0].numericValue().toDouble()))
else -> throw VmExecutionException("strange abs type ${args[0]}")
}
}
"max" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(args[0].type, numbers.max())
}
"min" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(args[0].type, numbers.min())
}
"avg" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(DataType.FLOAT, numbers.average())
}
"sum" -> {
val sum = args.map { it.numericValue().toDouble() }.sum()
when (args[0].type) {
DataType.UBYTE -> RuntimeValue(DataType.UWORD, sum)
DataType.BYTE -> RuntimeValue(DataType.WORD, sum)
DataType.UWORD -> RuntimeValue(DataType.UWORD, sum)
DataType.WORD -> RuntimeValue(DataType.WORD, sum)
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, sum)
else -> throw VmExecutionException("weird sum type ${args[0]}")
}
}
"any" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.any { it != 0.0 }) 1 else 0)
}
"all" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.all { it != 0.0 }) 1 else 0)
}
"swap" ->
throw VmExecutionException("swap() cannot be implemented as a function")
"strlen" -> {
val zeroIndex = args[0].str!!.indexOf(0.toChar())
if (zeroIndex >= 0)
RuntimeValue(DataType.UBYTE, zeroIndex)
else
RuntimeValue(DataType.UBYTE, args[0].str!!.length)
}
"memset" -> {
val target = args[0].array!!
val amount = args[1].integerValue()
val value = args[2].integerValue()
for (i in 0 until amount) {
target[i] = value
}
null
}
"memsetw" -> {
val target = args[0].array!!
val amount = args[1].integerValue()
val value = args[2].integerValue()
for (i in 0 until amount step 2) {
target[i * 2] = value and 255
target[i * 2 + 1] = value ushr 8
}
null
}
"memcopy" -> {
val source = args[0].array!!
val dest = args[1].array!!
val amount = args[2].integerValue()
for(i in 0 until amount) {
dest[i] = source[i]
}
null
}
"mkword" -> {
val result = (args[1].integerValue() shl 8) or args[0].integerValue()
RuntimeValue(DataType.UWORD, result)
}
"set_carry" -> {
statusflags.carry=true
null
}
"clear_carry" -> {
statusflags.carry=false
null
}
"set_irqd" -> {
statusflags.irqd=true
null
}
"clear_irqd" -> {
statusflags.irqd=false
null
}
"read_flags" -> {
val carry = if(statusflags.carry) 1 else 0
val zero = if(statusflags.zero) 2 else 0
val irqd = if(statusflags.irqd) 4 else 0
val negative = if(statusflags.negative) 128 else 0
RuntimeValue(DataType.UBYTE, carry or zero or irqd or negative)
}
"rsave" -> {
statusFlagsSave.push(statusflags)
registerAsave.push(runtimeVariables.get(program.namespace, Register.A.name))
registerXsave.push(runtimeVariables.get(program.namespace, Register.X.name))
registerYsave.push(runtimeVariables.get(program.namespace, Register.Y.name))
null
}
"rrestore" -> {
val flags = statusFlagsSave.pop()
statusflags.carry = flags.carry
statusflags.negative = flags.negative
statusflags.zero = flags.zero
statusflags.irqd = flags.irqd
runtimeVariables.set(program.namespace, Register.A.name, registerAsave.pop())
runtimeVariables.set(program.namespace, Register.X.name, registerXsave.pop())
runtimeVariables.set(program.namespace, Register.Y.name, registerYsave.pop())
null
}
else -> TODO("builtin function $name")
}
}
}

View File

@ -1,204 +0,0 @@
package prog8.vm.astvm
import prog8.ast.base.DataType
import prog8.vm.RuntimeValue
import java.lang.Math.toDegrees
import java.lang.Math.toRadians
import java.util.*
import kotlin.math.*
import kotlin.random.Random
class BuiltinFunctions {
private val rnd = Random(0)
private val statusFlagsSave = Stack<StatusFlags>()
fun performBuiltinFunction(name: String, args: List<RuntimeValue>, statusflags: StatusFlags): RuntimeValue? {
return when (name) {
"rnd" -> RuntimeValue(DataType.UBYTE, rnd.nextInt() and 255)
"rndw" -> RuntimeValue(DataType.UWORD, rnd.nextInt() and 65535)
"rndf" -> RuntimeValue(DataType.FLOAT, rnd.nextDouble())
"lsb" -> RuntimeValue(DataType.UBYTE, args[0].integerValue() and 255)
"msb" -> RuntimeValue(DataType.UBYTE, (args[0].integerValue() ushr 8) and 255)
"sin" -> RuntimeValue(DataType.FLOAT, sin(args[0].numericValue().toDouble()))
"sin8" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (127.0 * sin(rad)).toShort())
}
"sin8u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort())
}
"sin16" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (32767.0 * sin(rad)).toShort())
}
"sin16u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (32768.0 + 32767.5 * sin(rad)).toShort())
}
"cos" -> RuntimeValue(DataType.FLOAT, cos(args[0].numericValue().toDouble()))
"cos8" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (127.0 * cos(rad)).toShort())
}
"cos8u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toShort())
}
"cos16" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (32767.0 * cos(rad)).toShort())
}
"cos16u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (32768.0 + 32767.5 * cos(rad)).toShort())
}
"tan" -> RuntimeValue(DataType.FLOAT, tan(args[0].numericValue().toDouble()))
"atan" -> RuntimeValue(DataType.FLOAT, atan(args[0].numericValue().toDouble()))
"ln" -> RuntimeValue(DataType.FLOAT, ln(args[0].numericValue().toDouble()))
"log2" -> RuntimeValue(DataType.FLOAT, log2(args[0].numericValue().toDouble()))
"sqrt" -> RuntimeValue(DataType.FLOAT, sqrt(args[0].numericValue().toDouble()))
"sqrt16" -> RuntimeValue(DataType.UBYTE, sqrt(args[0].wordval!!.toDouble()).toInt())
"rad" -> RuntimeValue(DataType.FLOAT, toRadians(args[0].numericValue().toDouble()))
"deg" -> RuntimeValue(DataType.FLOAT, toDegrees(args[0].numericValue().toDouble()))
"round" -> RuntimeValue(DataType.FLOAT, round(args[0].numericValue().toDouble()))
"floor" -> RuntimeValue(DataType.FLOAT, floor(args[0].numericValue().toDouble()))
"ceil" -> RuntimeValue(DataType.FLOAT, ceil(args[0].numericValue().toDouble()))
"rol" -> {
val (result, newCarry) = args[0].rol(statusflags.carry)
statusflags.carry = newCarry
return result
}
"rol2" -> args[0].rol2()
"ror" -> {
val (result, newCarry) = args[0].ror(statusflags.carry)
statusflags.carry = newCarry
return result
}
"ror2" -> args[0].ror2()
"lsl" -> args[0].shl()
"lsr" -> args[0].shr()
"abs" -> {
when (args[0].type) {
DataType.UBYTE -> args[0]
DataType.BYTE -> RuntimeValue(DataType.UBYTE, abs(args[0].numericValue().toDouble()))
DataType.UWORD -> args[0]
DataType.WORD -> RuntimeValue(DataType.UWORD, abs(args[0].numericValue().toDouble()))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, abs(args[0].numericValue().toDouble()))
else -> TODO("strange abs type")
}
}
"max" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(args[0].type, numbers.max())
}
"min" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(args[0].type, numbers.min())
}
"avg" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(DataType.FLOAT, numbers.average())
}
"sum" -> {
val sum = args.map { it.numericValue().toDouble() }.sum()
when (args[0].type) {
DataType.UBYTE -> RuntimeValue(DataType.UWORD, sum)
DataType.BYTE -> RuntimeValue(DataType.WORD, sum)
DataType.UWORD -> RuntimeValue(DataType.UWORD, sum)
DataType.WORD -> RuntimeValue(DataType.WORD, sum)
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, sum)
else -> TODO("weird sum type")
}
}
"any" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.any { it != 0.0 }) 1 else 0)
}
"all" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.all { it != 0.0 }) 1 else 0)
}
"swap" ->
throw VmExecutionException("swap() cannot be implemented as a function")
"strlen" -> {
val zeroIndex = args[0].str!!.indexOf(0.toChar())
if (zeroIndex >= 0)
RuntimeValue(DataType.UBYTE, zeroIndex)
else
RuntimeValue(DataType.UBYTE, args[0].str!!.length)
}
"memset" -> {
val target = args[0].array!!
val amount = args[1].integerValue()
val value = args[2].integerValue()
for (i in 0 until amount) {
target[i] = value
}
null
}
"memsetw" -> {
val target = args[0].array!!
val amount = args[1].integerValue()
val value = args[2].integerValue()
for (i in 0 until amount step 2) {
target[i * 2] = value and 255
target[i * 2 + 1] = value ushr 8
}
null
}
"memcopy" -> {
val source = args[0].array!!
val dest = args[1].array!!
val amount = args[2].integerValue()
for(i in 0 until amount) {
dest[i] = source[i]
}
null
}
"mkword" -> {
val result = (args[1].integerValue() shl 8) or args[0].integerValue()
RuntimeValue(DataType.UWORD, result)
}
"set_carry" -> {
statusflags.carry=true
null
}
"clear_carry" -> {
statusflags.carry=false
null
}
"set_irqd" -> {
statusflags.irqd=true
null
}
"clear_irqd" -> {
statusflags.irqd=false
null
}
"read_flags" -> {
val carry = if(statusflags.carry) 1 else 0
val zero = if(statusflags.zero) 2 else 0
val irqd = if(statusflags.irqd) 4 else 0
val negative = if(statusflags.negative) 128 else 0
RuntimeValue(DataType.UBYTE, carry or zero or irqd or negative)
}
"rsave" -> {
statusFlagsSave.push(statusflags)
null
}
"rrestore" -> {
val flags = statusFlagsSave.pop()
statusflags.carry = flags.carry
statusflags.negative = flags.negative
statusflags.zero = flags.zero
statusflags.irqd = flags.irqd
null
}
else -> TODO("builtin function $name")
}
}
}

View File

@ -14,8 +14,9 @@ import prog8.vm.RuntimeValueRange
import kotlin.math.abs
class EvalContext(val program: Program, val mem: Memory, val statusflags: StatusFlags,
val runtimeVars: RuntimeVariables, val functions: BuiltinFunctions,
val executeSubroutine: (sub: Subroutine, args: List<RuntimeValue>, startlabel: Label?) -> List<RuntimeValue>)
val runtimeVars: RuntimeVariables,
val performBuiltinFunction: (String, List<RuntimeValue>, StatusFlags) -> RuntimeValue?,
val executeSubroutine: (sub: Subroutine, args: List<RuntimeValue>, startlabel: Label?) -> RuntimeValue?)
fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
val constval = expr.constValue(ctx.program)
@ -116,13 +117,12 @@ fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
val args = expr.arglist.map { evaluate(it, ctx) }
return when(sub) {
is Subroutine -> {
val results = ctx.executeSubroutine(sub, args, null)
if(results.size!=1)
throw VmExecutionException("expected 1 result from functioncall $expr")
results[0]
val result = ctx.executeSubroutine(sub, args, null)
?: throw VmExecutionException("expected a result from functioncall $expr")
result
}
is BuiltinFunctionStatementPlaceholder -> {
val result = ctx.functions.performBuiltinFunction(sub.name, args, ctx.statusflags)
val result = ctx.performBuiltinFunction(sub.name, args, ctx.statusflags)
?: throw VmExecutionException("expected 1 result from functioncall $expr")
result
}

View File

@ -7,6 +7,7 @@ import java.awt.*
import java.awt.event.KeyEvent
import java.awt.event.KeyListener
import java.awt.image.BufferedImage
import java.util.*
import javax.swing.JFrame
import javax.swing.JPanel
import javax.swing.Timer
@ -18,6 +19,7 @@ class BitmapScreenPanel : KeyListener, JPanel() {
private val g2d = image.graphics as Graphics2D
private var cursorX: Int=0
private var cursorY: Int=0
val keyboardBuffer: Deque<Char> = LinkedList<Char>()
init {
val size = Dimension(image.width * SCALING, image.height * SCALING)
@ -30,14 +32,14 @@ class BitmapScreenPanel : KeyListener, JPanel() {
addKeyListener(this)
}
override fun keyTyped(p0: KeyEvent?) {}
override fun keyTyped(p0: KeyEvent) {
keyboardBuffer.add(p0.keyChar)
}
override fun keyPressed(p0: KeyEvent?) {
println("pressed: $p0.k")
override fun keyPressed(p0: KeyEvent) {
}
override fun keyReleased(p0: KeyEvent?) {
println("released: $p0")
}
override fun paint(graphics: Graphics?) {
@ -61,49 +63,70 @@ class BitmapScreenPanel : KeyListener, JPanel() {
g2d.color = Colors.palette[color % Colors.palette.size]
g2d.drawLine(x1, y1, x2, y2)
}
fun printText(text: String, color: Short, lowercase: Boolean) {
fun printText(text: String, lowercase: Boolean, inverseVideo: Boolean=false) {
val t2 = text.substringBefore(0.toChar())
val lines = t2.split('\n')
for(line in lines.withIndex()) {
printTextSingleLine(line.value, color, lowercase)
val petscii = Petscii.encodePetscii(line.value, lowercase)
petscii.forEach { printPetscii(it, inverseVideo) }
if(line.index<lines.size-1) {
cursorX=0
cursorY++
}
}
}
private fun printTextSingleLine(text: String, color: Short, lowercase: Boolean) {
for(clearx in cursorX until cursorX+text.length) {
g2d.clearRect(8*clearx, 8*y, 8, 8)
}
for(sc in Petscii.encodeScreencode(text, lowercase)) {
setChar(cursorX, cursorY, sc, color)
cursorX++
if(cursorX>=(SCREENWIDTH /8)) {
cursorY++
cursorX=0
printPetscii(13) // newline
}
}
}
fun printChar(char: Short) {
fun printPetscii(char: Short, inverseVideo: Boolean=false) {
if(char==13.toShort() || char==141.toShort()) {
cursorX=0
cursorY++
} else {
setChar(cursorX, cursorY, char, 1)
setPetscii(cursorX, cursorY, char, 1, inverseVideo)
cursorX++
if (cursorX >= (SCREENWIDTH / 8)) {
cursorY++
cursorX = 0
}
}
while(cursorY>=(SCREENHEIGHT/8)) {
// scroll the screen up because the cursor went past the last line
Thread.sleep(10)
val screen = image.copy()
val graphics = image.graphics as Graphics2D
graphics.drawImage(screen, 0, -8, null)
val color = graphics.color
graphics.color = Colors.palette[6]
graphics.fillRect(0, 24*8, SCREENWIDTH, 25*8)
graphics.color=color
cursorY--
}
}
fun setChar(x: Int, y: Int, screenCode: Short, color: Short) {
fun writeTextAt(x: Int, y: Int, text: String, color: Short, lowercase: Boolean, inverseVideo: Boolean=false) {
val colorIdx = (color % Colors.palette.size).toShort()
var xx=x
for(clearx in xx until xx+text.length) {
g2d.clearRect(8*clearx, 8*y, 8, 8)
}
for(sc in Petscii.encodePetscii(text, lowercase)) {
if(sc==0.toShort())
break
setPetscii(xx++, y, sc, colorIdx, inverseVideo)
}
}
fun setPetscii(x: Int, y: Int, petscii: Short, color: Short, inverseVideo: Boolean) {
g2d.clearRect(8*x, 8*y, 8, 8)
val colorIdx = (color % Colors.palette.size).toShort()
val coloredImage = Charset.getColoredChar(screenCode, colorIdx)
val screencode = Petscii.petscii2scr(petscii, inverseVideo)
val coloredImage = Charset.getColoredChar(screencode, colorIdx)
g2d.drawImage(coloredImage, 8*x, 8*y , null)
}
fun setChar(x: Int, y: Int, screencode: Short, color: Short) {
g2d.clearRect(8*x, 8*y, 8, 8)
val colorIdx = (color % Colors.palette.size).toShort()
val coloredImage = Charset.getColoredChar(screencode, colorIdx)
g2d.drawImage(coloredImage, 8*x, 8*y , null)
}
@ -116,20 +139,6 @@ class BitmapScreenPanel : KeyListener, JPanel() {
return Pair(cursorX, cursorY)
}
fun writeText(x: Int, y: Int, text: String, color: Short, lowercase: Boolean) {
val colorIdx = (color % Colors.palette.size).toShort()
var xx=x
for(clearx in xx until xx+text.length) {
g2d.clearRect(8*clearx, 8*y, 8, 8)
}
for(sc in Petscii.encodeScreencode(text, lowercase)) {
if(sc==0.toShort())
break
setChar(xx++, y, sc, colorIdx)
}
}
companion object {
const val SCREENWIDTH = 320
const val SCREENHEIGHT = 200
@ -140,11 +149,12 @@ class BitmapScreenPanel : KeyListener, JPanel() {
class ScreenDialog(title: String) : JFrame(title) {
val canvas = BitmapScreenPanel()
val keyboardBuffer = canvas.keyboardBuffer
init {
val borderWidth = 16
layout = GridBagLayout()
defaultCloseOperation = JFrame.EXIT_ON_CLOSE
defaultCloseOperation = EXIT_ON_CLOSE
isResizable = false
// the borders (top, left, right, bottom)
@ -189,3 +199,12 @@ class ScreenDialog(title: String) : JFrame(title) {
repaintTimer.start()
}
}
private fun BufferedImage.copy(): BufferedImage {
val bcopy = BufferedImage(this.width, this.height, this.type)
val g = bcopy.graphics
g.drawImage(this, 0, 0, null)
g.dispose()
return bcopy
}

View File

@ -20,7 +20,7 @@ import java.util.*
import kotlin.math.*
enum class Syscall(val callNr: Short) {
internal enum class Syscall(val callNr: Short) {
VM_WRITE_MEMCHR(10), // print a single char from the memory address popped from stack
VM_WRITE_MEMSTR(11), // print a 0-terminated petscii string from the memory address popped from stack
VM_WRITE_NUM(12), // pop from the evaluation stack and print it as a number
@ -107,10 +107,9 @@ enum class Syscall(val callNr: Short) {
SYSASM_c64flt_print_f(214),
}
internal val syscallNames = enumValues<Syscall>().map { it.name }.toSet()
val syscallNames = enumValues<Syscall>().map { it.name }.toSet()
val syscallsForStackVm = setOf(
internal val syscallsForStackVm = setOf(
Syscall.VM_WRITE_MEMCHR,
Syscall.VM_WRITE_MEMSTR,
Syscall.VM_WRITE_NUM,
@ -123,13 +122,13 @@ val syscallsForStackVm = setOf(
Syscall.VM_GFX_LINE
)
class VmExecutionException(msg: String?) : Exception(msg)
internal class VmExecutionException(msg: String?) : Exception(msg)
class VmTerminationException(msg: String?) : Exception(msg)
internal class VmTerminationException(msg: String?) : Exception(msg)
class VmBreakpointException : Exception("breakpoint")
internal class VmBreakpointException : Exception("breakpoint")
class MyStack<T> : Stack<T>() {
internal class MyStack<T> : Stack<T>() {
fun peek(amount: Int) : List<T> {
return this.toList().subList(max(0, size-amount), size)
}
@ -141,7 +140,6 @@ class MyStack<T> : Stack<T>() {
}
}
class StackVm(private var traceOutputFile: String?) {
val mem = Memory(::memread, ::memwrite)
var P_carry: Boolean = false
@ -156,9 +154,9 @@ class StackVm(private var traceOutputFile: String?) {
private set
var memoryPointers = mutableMapOf<String, Pair<Int, DataType>>() // all named pointers
private set
var evalstack = MyStack<RuntimeValue>()
internal var evalstack = MyStack<RuntimeValue>()
private set
var callstack = MyStack<Int>()
internal var callstack = MyStack<Int>()
private set
private var program = listOf<Instruction>()
private var labels = emptyMap<String, Int>()
@ -204,7 +202,7 @@ class StackVm(private var traceOutputFile: String?) {
throw VmExecutionException("program contains variable(s) for the reserved registers A/X/Y")
// define the 'registers'
variables["A"] = RuntimeValue(DataType.UBYTE, 0)
variables["X"] = RuntimeValue(DataType.UBYTE, 0)
variables["X"] = RuntimeValue(DataType.UBYTE, 255)
variables["Y"] = RuntimeValue(DataType.UBYTE, 0)
initMemory(program.memory)
@ -1865,8 +1863,8 @@ class StackVm(private var traceOutputFile: String?) {
}
Opcode.RRESTORE -> {
variables["A"] = evalstack.pop()
variables["X"] = evalstack.pop()
variables["Y"] = evalstack.pop()
variables["X"] = evalstack.pop()
P_carry = evalstack.pop().asBoolean
P_irqd = evalstack.pop().asBoolean
}
@ -1928,7 +1926,7 @@ class StackVm(private var traceOutputFile: String?) {
}
"c64.CHROUT" -> {
val sc=variables.getValue("A").integerValue()
canvas?.printChar(sc.toShort())
canvas?.printPetscii(sc.toShort())
callstack.pop()
}
"c64.GETIN" -> {
@ -2029,7 +2027,7 @@ class StackVm(private var traceOutputFile: String?) {
val color = evalstack.pop()
val (cy, cx) = evalstack.pop2()
val text = heap.get(textPtr)
canvas?.writeText(cx.integerValue(), cy.integerValue(), text.str!!, color.integerValue().toShort(), true)
canvas?.writeTextAt(cx.integerValue(), cy.integerValue(), text.str!!, color.integerValue().toShort(), true)
}
Syscall.FUNC_RND -> evalstack.push(RuntimeValue(DataType.UBYTE, rnd.nextInt() and 255))
Syscall.FUNC_RNDW -> evalstack.push(RuntimeValue(DataType.UWORD, rnd.nextInt() and 65535))
@ -2311,54 +2309,54 @@ class StackVm(private var traceOutputFile: String?) {
Syscall.SYSASM_c64scr_print -> {
val straddr = variables.getValue("A").integerValue() + 256*variables.getValue("Y").integerValue()
val str = heap.get(straddr).str!!
canvas?.printText(str, 1, true)
canvas?.printText(str, true)
}
Syscall.SYSASM_c64scr_print_ub -> {
val num = variables.getValue("A").integerValue()
canvas?.printText(num.toString(), 1, true)
canvas?.printText(num.toString(), true)
}
Syscall.SYSASM_c64scr_print_ub0 -> {
val num = variables.getValue("A").integerValue()
canvas?.printText("%03d".format(num), 1, true)
canvas?.printText("%03d".format(num), true)
}
Syscall.SYSASM_c64scr_print_b -> {
val num = variables.getValue("A").integerValue()
if(num<=127)
canvas?.printText(num.toString(), 1, true)
canvas?.printText(num.toString(), true)
else
canvas?.printText("-${256-num}", 1, true)
canvas?.printText("-${256-num}", true)
}
Syscall.SYSASM_c64scr_print_uw -> {
val lo = variables.getValue("A").integerValue()
val hi = variables.getValue("Y").integerValue()
val number = lo+256*hi
canvas?.printText(number.toString(), 1, true)
canvas?.printText(number.toString(), true)
}
Syscall.SYSASM_c64scr_print_uw0 -> {
val lo = variables.getValue("A").integerValue()
val hi = variables.getValue("Y").integerValue()
val number = lo+256*hi
canvas?.printText("%05d".format(number), 1, true)
canvas?.printText("%05d".format(number), true)
}
Syscall.SYSASM_c64scr_print_uwhex -> {
val prefix = if(this.P_carry) "$" else ""
val lo = variables.getValue("A").integerValue()
val hi = variables.getValue("Y").integerValue()
val number = lo+256*hi
canvas?.printText("$prefix${number.toString(16).padStart(4, '0')}", 1, true)
canvas?.printText("$prefix${number.toString(16).padStart(4, '0')}", true)
}
Syscall.SYSASM_c64scr_print_w -> {
val lo = variables.getValue("A").integerValue()
val hi = variables.getValue("Y").integerValue()
val number = lo+256*hi
if(number<=32767)
canvas?.printText(number.toString(), 1, true)
canvas?.printText(number.toString(), true)
else
canvas?.printText("-${65536-number}", 1, true)
canvas?.printText("-${65536-number}", true)
}
Syscall.SYSASM_c64flt_print_f -> {
val number = variables.getValue("c64flt.print_f.value").numericValue()
canvas?.printText(number.toString(), 1, true)
canvas?.printText(number.toString(), true)
}
Syscall.SYSASM_c64scr_setcc -> {
val x = variables.getValue("c64scr.setcc.column").integerValue()
@ -2412,12 +2410,12 @@ class StackVm(private var traceOutputFile: String?) {
irqStoredCarry = P_carry
irqStoredTraceOutputFile = traceOutputFile
if(irqStartInstructionPtr>=0)
currentInstructionPtr = irqStartInstructionPtr
currentInstructionPtr = if(irqStartInstructionPtr>=0)
irqStartInstructionPtr
else {
if(program.last().opcode!=Opcode.RETURN)
throw VmExecutionException("last instruction in program should be RETURN for irq handler")
currentInstructionPtr = program.size-1
program.size-1
}
callstack = MyStack()
evalstack = MyStack()

View File

@ -17,39 +17,27 @@ class TestRuntimeValue {
@Test
fun testValueRanges() {
assertEquals(100, RuntimeValue(DataType.UBYTE, 100).integerValue())
assertEquals(100, RuntimeValue(DataType.BYTE, 100).integerValue())
assertEquals(10000, RuntimeValue(DataType.UWORD, 10000).integerValue())
assertEquals(10000, RuntimeValue(DataType.WORD, 10000).integerValue())
assertEquals(100.11, RuntimeValue(DataType.FLOAT, 100.11).numericValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 0).integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 255).integerValue())
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UBYTE, -1)}
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UBYTE, 256)}
assertEquals(200, RuntimeValue(DataType.UBYTE, 200).integerValue())
assertEquals(-56, RuntimeValue(DataType.BYTE, 200).integerValue())
assertEquals(50000, RuntimeValue(DataType.UWORD, 50000).integerValue())
assertEquals(-15536, RuntimeValue(DataType.WORD, 50000).integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 0).integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, -128).integerValue())
assertEquals(127, RuntimeValue(DataType.BYTE, 127).integerValue())
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.BYTE, -129)}
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.BYTE, 128)}
assertEquals(44, RuntimeValue(DataType.UBYTE, 300).integerValue())
assertEquals(44, RuntimeValue(DataType.BYTE, 300).integerValue())
assertEquals(144, RuntimeValue(DataType.UBYTE, 400).integerValue())
assertEquals(-112, RuntimeValue(DataType.BYTE, 400).integerValue())
assertEquals(34463, RuntimeValue(DataType.UWORD, 99999).integerValue())
assertEquals(-31073, RuntimeValue(DataType.WORD, 99999).integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 0).integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 65535).integerValue())
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UWORD, -1)}
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UWORD, 65536)}
assertEquals(156, RuntimeValue(DataType.UBYTE, -100).integerValue())
assertEquals(-100, RuntimeValue(DataType.BYTE, -100).integerValue())
assertEquals(55536, RuntimeValue(DataType.UWORD, -10000).integerValue())
assertEquals(-10000, RuntimeValue(DataType.WORD, -10000).integerValue())
assertEquals(-100.11, RuntimeValue(DataType.FLOAT, -100.11).numericValue())
assertEquals(56, RuntimeValue(DataType.UBYTE, -200).integerValue())
assertEquals(56, RuntimeValue(DataType.BYTE, -200).integerValue())
assertEquals(45536, RuntimeValue(DataType.UWORD, -20000).integerValue())
assertEquals(-20000, RuntimeValue(DataType.WORD, -20000).integerValue())
assertEquals(212, RuntimeValue(DataType.UBYTE, -300).integerValue())
assertEquals(-44, RuntimeValue(DataType.BYTE, -300).integerValue())
assertEquals(42184, RuntimeValue(DataType.UWORD, -88888).integerValue())
assertEquals(-23352, RuntimeValue(DataType.WORD, -88888).integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 0).integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, -32768).integerValue())
assertEquals(32767, RuntimeValue(DataType.WORD, 32767).integerValue())
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.WORD, -32769)}
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.WORD, 32768)}
}
@Test
@ -60,10 +48,6 @@ class TestRuntimeValue {
assertFalse(RuntimeValue(DataType.WORD, 0).asBoolean)
assertFalse(RuntimeValue(DataType.UWORD, 0).asBoolean)
assertFalse(RuntimeValue(DataType.FLOAT, 0.0).asBoolean)
assertFalse(RuntimeValue(DataType.BYTE, 256).asBoolean)
assertFalse(RuntimeValue(DataType.UBYTE, 256).asBoolean)
assertFalse(RuntimeValue(DataType.WORD, 65536).asBoolean)
assertFalse(RuntimeValue(DataType.UWORD, 65536).asBoolean)
assertTrue(RuntimeValue(DataType.BYTE, 42).asBoolean)
assertTrue(RuntimeValue(DataType.UBYTE, 42).asBoolean)
@ -71,9 +55,7 @@ class TestRuntimeValue {
assertTrue(RuntimeValue(DataType.UWORD, 42).asBoolean)
assertTrue(RuntimeValue(DataType.FLOAT, 42.0).asBoolean)
assertTrue(RuntimeValue(DataType.BYTE, -42).asBoolean)
assertTrue(RuntimeValue(DataType.UBYTE, -42).asBoolean)
assertTrue(RuntimeValue(DataType.WORD, -42).asBoolean)
assertTrue(RuntimeValue(DataType.UWORD, -42).asBoolean)
assertTrue(RuntimeValue(DataType.FLOAT, -42.0).asBoolean)
}
@ -245,4 +227,155 @@ class TestRuntimeValue {
}
val result = RuntimeValue(DataType.FLOAT, 233.333).add(RuntimeValue(DataType.FLOAT, 1.234))
}
@Test
fun arithmetictestUbyte() {
assertEquals(255, RuntimeValue(DataType.UBYTE, 200).add(RuntimeValue(DataType.UBYTE, 55)).integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 200).add(RuntimeValue(DataType.UBYTE, 56)).integerValue())
assertEquals(1, RuntimeValue(DataType.UBYTE, 200).add(RuntimeValue(DataType.UBYTE, 57)).integerValue())
assertEquals(1, RuntimeValue(DataType.UBYTE, 2).sub(RuntimeValue(DataType.UBYTE, 1)).integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 2).sub(RuntimeValue(DataType.UBYTE, 2)).integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 2).sub(RuntimeValue(DataType.UBYTE, 3)).integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 254).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 255).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 1).dec().integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 0).dec().integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 0).inv().integerValue())
assertEquals(0b00110011, RuntimeValue(DataType.UBYTE, 0b11001100).inv().integerValue())
// assertEquals(0, RuntimeValue(DataType.UBYTE, 0).neg().integerValue())
// assertEquals(0, RuntimeValue(DataType.UBYTE, 0).neg().integerValue())
assertEquals(1, RuntimeValue(DataType.UBYTE, 0).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 1).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 111).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 255).not().integerValue())
assertEquals(200, RuntimeValue(DataType.UBYTE, 20).mul(RuntimeValue(DataType.UBYTE, 10)).integerValue())
assertEquals(144, RuntimeValue(DataType.UBYTE, 20).mul(RuntimeValue(DataType.UBYTE, 20)).integerValue())
assertEquals(25, RuntimeValue(DataType.UBYTE, 5).pow(RuntimeValue(DataType.UBYTE, 2)).integerValue())
assertEquals(125, RuntimeValue(DataType.UBYTE, 5).pow(RuntimeValue(DataType.UBYTE, 3)).integerValue())
assertEquals(113, RuntimeValue(DataType.UBYTE, 5).pow(RuntimeValue(DataType.UBYTE, 4)).integerValue())
assertEquals(100, RuntimeValue(DataType.UBYTE, 50).shl().integerValue())
assertEquals(200, RuntimeValue(DataType.UBYTE, 100).shl().integerValue())
assertEquals(144, RuntimeValue(DataType.UBYTE, 200).shl().integerValue())
}
@Test
fun arithmetictestUWord() {
assertEquals(65535, RuntimeValue(DataType.UWORD, 60000).add(RuntimeValue(DataType.UWORD, 5535)).integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 60000).add(RuntimeValue(DataType.UWORD, 5536)).integerValue())
assertEquals(1, RuntimeValue(DataType.UWORD, 60000).add(RuntimeValue(DataType.UWORD, 5537)).integerValue())
assertEquals(1, RuntimeValue(DataType.UWORD, 2).sub(RuntimeValue(DataType.UWORD, 1)).integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 2).sub(RuntimeValue(DataType.UWORD, 2)).integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 2).sub(RuntimeValue(DataType.UWORD, 3)).integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 65534).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 65535).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 1).dec().integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 0).dec().integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 0).inv().integerValue())
assertEquals(0b0011001101010101, RuntimeValue(DataType.UWORD, 0b1100110010101010).inv().integerValue())
// assertEquals(0, RuntimeValue(DataType.UWORD, 0).neg().integerValue())
// assertEquals(0, RuntimeValue(DataType.UWORD, 0).neg().integerValue())
assertEquals(1, RuntimeValue(DataType.UWORD, 0).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 1).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 11111).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 65535).not().integerValue())
assertEquals(2000, RuntimeValue(DataType.UWORD, 200).mul(RuntimeValue(DataType.UWORD, 10)).integerValue())
assertEquals(40000, RuntimeValue(DataType.UWORD, 200).mul(RuntimeValue(DataType.UWORD, 200)).integerValue())
assertEquals(14464, RuntimeValue(DataType.UWORD, 200).mul(RuntimeValue(DataType.UWORD, 400)).integerValue())
assertEquals(15625, RuntimeValue(DataType.UWORD, 5).pow(RuntimeValue(DataType.UWORD, 6)).integerValue())
assertEquals(12589, RuntimeValue(DataType.UWORD, 5).pow(RuntimeValue(DataType.UWORD, 7)).integerValue())
assertEquals(10000, RuntimeValue(DataType.UWORD, 5000).shl().integerValue())
assertEquals(60000, RuntimeValue(DataType.UWORD, 30000).shl().integerValue())
assertEquals(14464, RuntimeValue(DataType.UWORD, 40000).shl().integerValue())
}
@Test
fun arithmetictestByte() {
assertEquals(127, RuntimeValue(DataType.BYTE, 100).add(RuntimeValue(DataType.BYTE, 27)).integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, 100).add(RuntimeValue(DataType.BYTE, 28)).integerValue())
assertEquals(-127, RuntimeValue(DataType.BYTE, 100).add(RuntimeValue(DataType.BYTE, 29)).integerValue())
assertEquals(1, RuntimeValue(DataType.BYTE, 2).sub(RuntimeValue(DataType.BYTE, 1)).integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 2).sub(RuntimeValue(DataType.BYTE, 2)).integerValue())
assertEquals(-1, RuntimeValue(DataType.BYTE, 2).sub(RuntimeValue(DataType.BYTE, 3)).integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, -100).sub(RuntimeValue(DataType.BYTE, 28)).integerValue())
assertEquals(127, RuntimeValue(DataType.BYTE, -100).sub(RuntimeValue(DataType.BYTE, 29)).integerValue())
assertEquals(127, RuntimeValue(DataType.BYTE, 126).inc().integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, 127).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 1).dec().integerValue())
assertEquals(-1, RuntimeValue(DataType.BYTE, 0).dec().integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, -127).dec().integerValue())
assertEquals(127, RuntimeValue(DataType.BYTE, -128).dec().integerValue())
assertEquals(-1, RuntimeValue(DataType.BYTE, 0).inv().integerValue())
assertEquals(-103, RuntimeValue(DataType.BYTE, 0b01100110).inv().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 0).neg().integerValue())
assertEquals(-2, RuntimeValue(DataType.BYTE, 2).neg().integerValue())
assertEquals(1, RuntimeValue(DataType.BYTE, 0).not().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 1).not().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 111).not().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, -33).not().integerValue())
assertEquals(100, RuntimeValue(DataType.BYTE, 10).mul(RuntimeValue(DataType.BYTE, 10)).integerValue())
assertEquals(-56, RuntimeValue(DataType.BYTE, 20).mul(RuntimeValue(DataType.BYTE, 10)).integerValue())
assertEquals(25, RuntimeValue(DataType.BYTE, 5).pow(RuntimeValue(DataType.BYTE, 2)).integerValue())
assertEquals(125, RuntimeValue(DataType.BYTE, 5).pow(RuntimeValue(DataType.BYTE, 3)).integerValue())
assertEquals(113, RuntimeValue(DataType.BYTE, 5).pow(RuntimeValue(DataType.BYTE, 4)).integerValue())
assertEquals(100, RuntimeValue(DataType.BYTE, 50).shl().integerValue())
assertEquals(-56, RuntimeValue(DataType.BYTE, 100).shl().integerValue())
assertEquals(-2, RuntimeValue(DataType.BYTE, -1).shl().integerValue())
}
@Test
fun arithmetictestWorrd() {
assertEquals(32767, RuntimeValue(DataType.WORD, 32700).add(RuntimeValue(DataType.WORD, 67)).integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, 32700).add(RuntimeValue(DataType.WORD, 68)).integerValue())
assertEquals(-32767, RuntimeValue(DataType.WORD, 32700).add(RuntimeValue(DataType.WORD, 69)).integerValue())
assertEquals(1, RuntimeValue(DataType.WORD, 2).sub(RuntimeValue(DataType.WORD, 1)).integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 2).sub(RuntimeValue(DataType.WORD, 2)).integerValue())
assertEquals(-1, RuntimeValue(DataType.WORD, 2).sub(RuntimeValue(DataType.WORD, 3)).integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, -32700).sub(RuntimeValue(DataType.WORD, 68)).integerValue())
assertEquals(32767, RuntimeValue(DataType.WORD, -32700).sub(RuntimeValue(DataType.WORD, 69)).integerValue())
assertEquals(32767, RuntimeValue(DataType.WORD, 32766).inc().integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, 32767).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 1).dec().integerValue())
assertEquals(-1, RuntimeValue(DataType.WORD, 0).dec().integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, -32767).dec().integerValue())
assertEquals(32767, RuntimeValue(DataType.WORD, -32768).dec().integerValue())
assertEquals(-1, RuntimeValue(DataType.WORD, 0).inv().integerValue())
assertEquals(-103, RuntimeValue(DataType.WORD, 0b01100110).inv().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 0).neg().integerValue())
assertEquals(-2, RuntimeValue(DataType.WORD, 2).neg().integerValue())
assertEquals(1, RuntimeValue(DataType.WORD, 0).not().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 1).not().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 111).not().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, -33).not().integerValue())
assertEquals(10000, RuntimeValue(DataType.WORD, 100).mul(RuntimeValue(DataType.WORD, 100)).integerValue())
assertEquals(-25536, RuntimeValue(DataType.WORD, 200).mul(RuntimeValue(DataType.WORD, 200)).integerValue())
assertEquals(15625, RuntimeValue(DataType.WORD, 5).pow(RuntimeValue(DataType.WORD, 6)).integerValue())
assertEquals(-6487, RuntimeValue(DataType.WORD, 9).pow(RuntimeValue(DataType.WORD, 5)).integerValue())
assertEquals(18000, RuntimeValue(DataType.WORD, 9000).shl().integerValue())
assertEquals(-25536, RuntimeValue(DataType.WORD, 20000).shl().integerValue())
assertEquals(-2, RuntimeValue(DataType.WORD, -1).shl().integerValue())
}
}

View File

@ -413,10 +413,10 @@ class TestStackVmOpcodes {
testUnaryOperator(RuntimeValue(DataType.UBYTE, 123), Opcode.INV_BYTE, RuntimeValue(DataType.UBYTE, 0x84))
testUnaryOperator(RuntimeValue(DataType.UWORD, 4044), Opcode.INV_WORD, RuntimeValue(DataType.UWORD, 0xf033))
assertFailsWith<VmExecutionException> {
testUnaryOperator(RuntimeValue(DataType.BYTE, 123), Opcode.INV_BYTE, RuntimeValue(DataType.BYTE, 0x84))
testUnaryOperator(RuntimeValue(DataType.BYTE, 123), Opcode.INV_BYTE, RuntimeValue(DataType.BYTE, -124))
}
assertFailsWith<VmExecutionException> {
testUnaryOperator(RuntimeValue(DataType.WORD, 4044), Opcode.INV_WORD, RuntimeValue(DataType.WORD, 0xf033))
testUnaryOperator(RuntimeValue(DataType.WORD, 4044), Opcode.INV_WORD, RuntimeValue(DataType.WORD, -4043))
}
}

View File

@ -2,7 +2,9 @@
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="Python 3.7 (py3)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>

View File

@ -143,32 +143,39 @@ Finally: a **C-64 emulator** (or a real C-64 ofcourse) to run the programs on. T
of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
.. important::
**Building the compiler itself:**
**Building the compiler itself:** (*Only needed if you have not downloaded a pre-built 'fat-jar'*)
(re)building the compiler itself requires a recent Kotlin SDK.
The compiler is developed using the `IntelliJ IDEA <https://www.jetbrains.com/idea/>`_
IDE from Jetbrains, with the Kotlin plugin (free community edition of this IDE is available).
But a bare Kotlin SDK installation should work just as well.
A shell script (``create_compiler_jar.sh``) is provided to build and package the compiler from the command line.
If you have the 'fat-jar' you can run it with ``java -jar prog8compiler.jar``.
You can also use the Gradle build system to build the compiler (it will take care of
downloading all required libraries for you) by typing ``gradle build`` for instance.
The output of this gradle build will appear in the "./compiler/build/install/p8compile/" directory.
If you have the 'fat-jar' you can run it with ``java -jar prog8compiler.jar`` or just use
one of the scripts that are created by Gradle
The Gradle build system is used to build the compiler.
The most interesting gradle commands to run are probably:
``./gradlew check``
Builds the compiler code and runs all available checks and unit-tests.
``./gradlew installDist``
Builds the compiler and installs it with scripts to run it, in the directory
``./compiler/build/install/p8compile``
``./gradlew installShadowDist``
Creates a 'fat-jar' that contains the compiler and all dependencies,
and a few start scripts to run it.
Creates a 'fat-jar' that contains the compiler and all dependencies, in a single
executable .jar file, and includes few start scripts to run it.
The output can be found in ``.compiler/build/install/compiler-shadow/``
and you can launch the compiler with the script
``./compiler/build/install/compiler-shadow/bin/p8compile``.
``./gradlew shadowDistZip``
Creates a zipfile with the above in it, for easy distribution.
This file can be found in ``./compiler/build/distributions/``
For normal use, the ``installDist`` target should suffice and ater succesful completion
of that build task, you can start the compiler with:
``./compiler/build/install/p8compile/bin/p8compile <options> <sourcefile>``
(You should probably make an alias...)
.. note::
Development and testing is done on Linux, but the compiler should run on most

View File

@ -416,15 +416,26 @@ So ``if_cc goto target`` will directly translate into the single CPU instruction
Maybe in the future this will be a separate nested scope, but for now, that is
only possible when defining a subroutine.
when statement (jumptable)
^^^^^^^^^^^^^^^^^^^^^^^^^^
when statement ('jump table')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. attention::
TODO: docs for this this must still be written.
TODO: the code generator for this is not yet working.
Instead of writing a bunch of sequential if-elseif statements, it is more readable to
use a ``when`` statement. (It will also result in greatly improved assembly code generation)
Use a ``when`` statement if you have a set of fixed choices that each should result in a certain
action. It is possible to combine several choices to result in the same action::
Use a ``when`` statement if you have a set of choices that each should result in a certain
action. It's more readable (and results in faster code) than using a lot of if / else statements.
when value {
4 -> c64scr.print("four")
5 -> c64scr.print("five")
10,20,30 -> {
c64scr.print("ten or twenty or thirty")
}
else -> c64scr.print("don't know")
}
The when-*value* can be any expression but the choice values have to evaluate to
compile-time constant integers (bytes or words). They also have to be the same
datatype as the when-value, otherwise no efficient comparison can be done.
Assignments

View File

@ -444,11 +444,12 @@ Normal subroutines can only return zero or one return values.
However, the special ``asmsub`` routines (implemented in assembly code or referencing
a routine in kernel ROM) can return more than one return values, for instance a status
in the carry bit and a number in A, or a 16-bit value in A/Y registers.
Only for these kind of subroutines it is possible to write a multi value assignment to
store the resulting values::
var1, var2, var3 = asmsubroutine()
It is not possible to process the results of a call to these kind of routines
directly from the language, because only single value assignments are possible.
You can still call the subroutine and not store the results.
But if you want to do something with the values it returns, you'll have to write
a small block of custom inline assembly that does the call and stores the values
appropriately. Don't forget to save/restore the registers if required.
Subroutine definitions
@ -610,28 +611,28 @@ The XX corresponds to one of the eigth branching instructions so the possibiliti
``if_cs``, ``if_cc``, ``if_eq``, ``if_ne``, ``if_pl``, ``if_mi``, ``if_vs`` and ``if_vc``.
It can also be one of the four aliases that are easier to read: ``if_z``, ``if_nz``, ``if_pos`` and ``if_neg``.
when statement (jumptable)
^^^^^^^^^^^^^^^^^^^^^^^^^^
.. attention::
TODO: docs for this this must still be written.
TODO: the code generator for this is not yet working.
when statement ('jump table')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The structure of a when statement is like this::
The condition value can only be an integer datatype.
The choice values must be constant integer values.
when <expression> {
<value(s)> -> <statement(s)>
<value(s)> -> <statement(s)>
...
[ else -> <statement(s)> ]
}
code example::
The when-*value* can be any expression but the choice values have to evaluate to
compile-time constant integers (bytes or words).
The else part is optional.
Choices can result in a single statement or a block of multiple statements in which
case you have to use { } to enclose them::
when 4+A+Y {
10 -> {
c64scr.print("ten")
}
5 -> c64scr.print("five")
30 -> c64scr.print("thirty")
99 -> c64scr.print("nn")
55 -> {
; will be optimized away
}
else -> {
c64scr.print("!??!\n")
}
when value {
4 -> c64scr.print("four")
5 -> c64scr.print("five")
10,20,30 -> {
c64scr.print("ten or twenty or thirty")
}
else -> c64scr.print("don't know")
}

View File

@ -32,7 +32,8 @@
ubyte guess = lsb(c64utils.str2uword(input))
if guess==secretnumber {
return ending(true)
ending(true)
return
} else {
c64scr.print("\n\nThat is too ")
if guess<secretnumber
@ -42,7 +43,8 @@
}
}
return ending(false)
ending(false)
return
sub ending(ubyte success) {

View File

@ -43,8 +43,6 @@
while multiple < len(sieve) {
sieve[lsb(multiple)] = true
multiple += candidate_prime
; c64scr.print_uw(multiple) ; TODO
; c4.CHROUT('\n') ; TODO
}
return candidate_prime
}

View File

@ -5,10 +5,11 @@
~ main {
sub start() {
c64.SCROLY &= %11101111 ; blank the screen
c64utils.set_rasterirq_excl(40)
c64.SCROLY &= %11101111 ; blank the screen
c64utils.set_rasterirq_excl(40) ; register exclusive raster irq handler
while(true) {
; enjoy the moving bars :)
}
}
@ -20,22 +21,20 @@
const ubyte barheight = 4
ubyte[] colors = [6,2,4,5,15,7,1,13,3,12,8,11,9]
ubyte color = 0
ubyte ypos = 0
ubyte yanim = 0
sub irq() {
Y++ ; slight timing delay to avoid rasterline transition issues
ubyte rasterpos = c64.RASTER
if color!=len(colors) {
c64.EXTCOL = colors[color]
c64.RASTER += barheight ; next raster Irq for next color
color++
c64.RASTER = rasterpos+barheight
}
else {
ypos += 2
c64.EXTCOL = 0
color = 0
c64.RASTER = sin8u(ypos)/2+40
yanim += 2
c64.RASTER = sin8u(yanim)/2+30 ; new start of raster Irq
}
c64.SCROLY &= $7f ; set high bit of the raster pos to zero
}
}

View File

@ -75,105 +75,112 @@ waitkey:
}
sub move_left() {
drawBlock(xpos, ypos, 32)
if blocklogic.noCollision(xpos-1, ypos) {
xpos--
}
drawBlock(xpos, ypos, 160)
}
sub move_right() {
drawBlock(xpos, ypos, 32)
if blocklogic.noCollision(xpos+1, ypos) {
xpos++
}
drawBlock(xpos, ypos, 160)
}
sub move_down_faster() {
drawBlock(xpos, ypos, 32)
if blocklogic.noCollision(xpos, ypos+1) {
ypos++
}
drawBlock(xpos, ypos, 160)
}
sub drop_down_immediately() {
drawBlock(xpos, ypos, 32)
ubyte dropypos
for dropypos in ypos+1 to boardOffsetY+boardHeight-1 {
if not blocklogic.noCollision(xpos, dropypos) {
dropypos-- ; the furthest down that still fits
break
}
}
if dropypos>ypos {
ypos = dropypos
sound.blockdrop()
drawBlock(xpos, ypos, 160)
checkForLines()
spawnNextBlock()
score++
drawScore()
}
}
sub keypress(ubyte key) {
if key==157 or key==',' {
; move left
drawBlock(xpos, ypos, 32)
if blocklogic.noCollision(xpos-1, ypos) {
xpos--
}
drawBlock(xpos, ypos, 160)
}
else if key==29 or key=='/' {
; move right
drawBlock(xpos, ypos, 32)
if blocklogic.noCollision(xpos+1, ypos) {
xpos++
}
drawBlock(xpos, ypos, 160)
}
else if key==17 or key=='.' {
; move down faster
drawBlock(xpos, ypos, 32)
if blocklogic.noCollision(xpos, ypos+1) {
ypos++
}
drawBlock(xpos, ypos, 160)
}
else if key==145 or key==' ' {
; drop down immediately
drawBlock(xpos, ypos, 32)
ubyte dropypos
for dropypos in ypos+1 to boardOffsetY+boardHeight-1 {
if not blocklogic.noCollision(xpos, dropypos) {
dropypos-- ; the furthest down that still fits
break
when key {
157, ',' -> move_left()
29, '/' -> move_right()
17, '.' -> move_down_faster()
145, ' ' -> drop_down_immediately()
'z' -> {
; no joystick equivalent (there is only 1 fire button)
; rotate counter clockwise
drawBlock(xpos, ypos, 32)
if blocklogic.canRotateCCW(xpos, ypos) {
blocklogic.rotateCCW()
sound.blockrotate()
}
else if blocklogic.canRotateCCW(xpos-1, ypos) {
xpos--
blocklogic.rotateCCW()
sound.blockrotate()
}
else if blocklogic.canRotateCCW(xpos+1, ypos) {
xpos++
blocklogic.rotateCCW()
sound.blockrotate()
}
}
if dropypos>ypos {
ypos = dropypos
sound.blockdrop()
drawBlock(xpos, ypos, 160)
checkForLines()
spawnNextBlock()
score++
drawScore()
}
}
else if key=='z' { ; no joystick equivalent (there is only 1 fire button)
; rotate counter clockwise
drawBlock(xpos, ypos, 32)
if blocklogic.canRotateCCW(xpos, ypos) {
blocklogic.rotateCCW()
sound.blockrotate()
}
else if blocklogic.canRotateCCW(xpos-1, ypos) {
xpos--
blocklogic.rotateCCW()
sound.blockrotate()
}
else if blocklogic.canRotateCCW(xpos+1, ypos) {
xpos++
blocklogic.rotateCCW()
sound.blockrotate()
}
drawBlock(xpos, ypos, 160)
}
else if key=='x' {
; rotate clockwise
drawBlock(xpos, ypos, 32)
if blocklogic.canRotateCW(xpos, ypos) {
blocklogic.rotateCW()
sound.blockrotate()
}
else if blocklogic.canRotateCW(xpos-1, ypos) {
xpos--
blocklogic.rotateCW()
sound.blockrotate()
}
else if blocklogic.canRotateCW(xpos+1, ypos) {
xpos++
blocklogic.rotateCW()
sound.blockrotate()
}
drawBlock(xpos, ypos, 160)
}
else if key=='c' {
; hold
if holdingAllowed {
sound.swapping()
if holding<7 {
drawBlock(xpos, ypos, 32)
ubyte newholding = blocklogic.currentBlockNum
swapBlock(holding)
holding = newholding
holdingAllowed = false
} else {
holding = blocklogic.currentBlockNum
drawBlock(xpos, ypos, 32)
spawnNextBlock()
'x' -> {
; rotate clockwise
drawBlock(xpos, ypos, 32)
if blocklogic.canRotateCW(xpos, ypos) {
blocklogic.rotateCW()
sound.blockrotate()
}
else if blocklogic.canRotateCW(xpos-1, ypos) {
xpos--
blocklogic.rotateCW()
sound.blockrotate()
}
else if blocklogic.canRotateCW(xpos+1, ypos) {
xpos++
blocklogic.rotateCW()
sound.blockrotate()
}
drawBlock(xpos, ypos, 160)
}
'c' -> {
; hold
if holdingAllowed {
sound.swapping()
if holding<7 {
drawBlock(xpos, ypos, 32)
ubyte newholding = blocklogic.currentBlockNum
swapBlock(holding)
holding = newholding
holdingAllowed = false
} else {
holding = blocklogic.currentBlockNum
drawBlock(xpos, ypos, 32)
spawnNextBlock()
}
drawHoldBlock()
}
drawHoldBlock()
}
}
}

View File

@ -1,56 +1,21 @@
%import c64utils
%zeropage basicsafe
%option enable_floats
~ main {
sub start() {
A=10
Y=22
uword uw = A*Y
str teststring = "hello"
c64scr.print(&teststring)
when uw {
12345 -> {
A=44
}
12346 -> {
A=44
}
12347 -> {
A=44
}
else -> {
A=0
}
}
when 4+A+Y {
10 -> {
c64scr.print("ten")
}
5 -> c64scr.print("five")
30 -> c64scr.print("thirty")
31 -> c64scr.print("thirty1")
32 -> c64scr.print("thirty2")
33 -> c64scr.print("thirty3")
99 -> c64scr.print("nn")
55 -> {
; should be optimized away
}
56 -> {
; should be optimized away
}
57243 -> {
; should be optimized away
}
else -> {
c64scr.print("!??!\n")
c64scr.print("!??!!??!\n")
c64scr.print("!??!!??!!?!\n")
}
}
foo(42)
return
}
sub foo(ubyte arg) -> ubyte {
bar(arg)
return 33
}
sub bar(ubyte a2) {
;nothing
}
}

View File

@ -1,33 +0,0 @@
#!/usr/bin/env bash
# compile using regular Kotlin sdk command line tool
echo "Compiling the parser..."
java -jar ./parser/antlr/lib/antlr-4.7.2-complete.jar -o ./parser/src/prog8/parser -Xexact-output-dir -no-listener -no-visitor ./parser/antlr/prog8.g4
PARSER_CLASSES=./out/production/parser
COMPILER_JAR=prog8compiler.jar
ANTLR_RUNTIME=./parser/antlr/lib/antlr-runtime-4.7.2.jar
mkdir -p ${PARSER_CLASSES}
javac -d ${PARSER_CLASSES} -cp ${ANTLR_RUNTIME} ./parser/src/prog8/parser/prog8Lexer.java ./parser/src/prog8/parser/prog8Parser.java
echo "Compiling the compiler itself..."
JAVA_OPTS="-Xmx3G -Xms300M" kotlinc -verbose -include-runtime -d ${COMPILER_JAR} -jvm-target 1.8 -cp ${ANTLR_RUNTIME}:${PARSER_CLASSES} ./compiler/src/prog8
echo "Finalizing the compiler jar file..."
# add the antlr parser classes
jar ufe ${COMPILER_JAR} prog8.CompilerMainKt -C ${PARSER_CLASSES} prog8
# add the resources
jar uf ${COMPILER_JAR} -C ./compiler/res .
# add the antlr runtime classes
rm -rf antlr_runtime_extraction
mkdir antlr_runtime_extraction
(cd antlr_runtime_extraction; jar xf ../${ANTLR_RUNTIME})
jar uf ${COMPILER_JAR} -C antlr_runtime_extraction org
rm -rf antlr_runtime_extraction
echo "Done!"

View File

@ -1,9 +0,0 @@
@echo off
set PROG8CLASSPATH=./compiler/build/classes/kotlin/main;./compiler/build/resources/main;./parser/build/classes/java/main
set KOTLINPATH=%USERPROFILE%/.IdeaIC2019.1/config/plugins/Kotlin
set LIBJARS=%KOTLINPATH%/lib/kotlin-stdlib.jar;%KOTLINPATH%/lib/kotlin-reflect.jar;./parser/antlr/lib/antlr-runtime-4.7.2.jar
java -cp %PROG8CLASSPATH%;%LIBJARS% prog8.CompilerMainKt %*
@REM if you have created a .jar file using the 'create_compiler_jar' script, you can simply do: java -jar prog8compiler.jar

View File

@ -1,9 +0,0 @@
#!/usr/bin/env sh
PROG8CLASSPATH=./compiler/build/classes/kotlin/main:./compiler/build/resources/main:./parser/build/classes/java/main
KOTLINPATH=${HOME}/.IntelliJIdea2019.1/config/plugins/Kotlin
LIBJARS=${KOTLINPATH}/lib/kotlin-stdlib.jar:${KOTLINPATH}/lib/kotlin-reflect.jar:./parser/antlr/lib/antlr-runtime-4.7.2.jar
java -cp ${PROG8CLASSPATH}:${LIBJARS} prog8.CompilerMainKt $*
# if you have created a .jar file using the 'create_compiler_jar' script, you can simply do: java -jar prog8compiler.jar

View File

@ -1,9 +0,0 @@
@echo off
set PROG8CLASSPATH=./compiler/build/classes/kotlin/main;./compiler/build/resources/main
set KOTLINPATH=%USERPROFILE%/.IdeaIC2019.1/config/plugins/Kotlin
set LIBJARS=%KOTLINPATH%/lib/kotlin-stdlib.jar;%KOTLINPATH%/lib/kotlin-reflect.jar
java -cp %PROG8CLASSPATH%;%LIBJARS% prog8.vm.stackvm.MainKt %*
@REM if you have created a .jar file using the 'create_compiler_jar' script, you can simply do: java -jar prog8compiler.jar -vm

View File

@ -1,9 +0,0 @@
#!/usr/bin/env sh
PROG8CLASSPATH=./compiler/build/classes/kotlin/main:./compiler/build/resources/main
KOTLINPATH=${HOME}/.IntelliJIdea2019.1/config/plugins/Kotlin
LIBJARS=${KOTLINPATH}/lib/kotlin-stdlib.jar:${KOTLINPATH}/lib/kotlin-reflect.jar
java -cp ${PROG8CLASSPATH}:${LIBJARS} prog8.vm.stackvm.MainKt $*
# if you have created a .jar file using the 'create_compiler_jar' script, you can simply do: java -jar prog8compiler.jar -vm

View File

@ -121,9 +121,7 @@ datatype: 'ubyte' | 'byte' | 'uword' | 'word' | 'float' | 'str' | 'str_s' ;
arrayindex: '[' expression ']' ;
assignment : assign_targets '=' expression ;
assign_targets : assign_target (',' assign_target)* ;
assignment : assign_target '=' expression ;
augassignment :
assign_target operator=('+=' | '-=' | '/=' | '*=' | '**=' | '&=' | '|=' | '^=' | '%=' | '<<=' | '>>=' ) expression
@ -185,7 +183,7 @@ expression_list :
expression (',' EOL? expression)* // you can split the expression list over several lines
;
returnstmt : 'return' expression_list? ;
returnstmt : 'return' expression? ;
breakstmt : 'break';
@ -282,4 +280,4 @@ repeatloop: 'repeat' (statement | statement_block) EOL? 'until' expression ;
whenstmt: 'when' expression '{' EOL (when_choice | EOL) * '}' EOL? ;
when_choice: (expression | 'else' ) '->' (statement | statement_block ) ;
when_choice: (expression_list | 'else' ) '->' (statement | statement_block ) ;

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
#!/usr/bin/env sh
rm *.jar *.asm *.prg *.vm.txt *.vice-mon-list
rm -r build
rm -rf build out