Compare commits

..
34 Commits
Author SHA1 Message Date
Irmen de Jong 8a26b7b248 - fixed lookup of members in structs defined in another scope
- preserve order of variable definitions in the Ast (and thus, the output)
2019-07-13 23:03:22 +02:00
Irmen de Jong 87c28cfdbc restructure c64 machinedefinition 2019-07-13 03:16:48 +02:00
Irmen de Jong 1f5420010d prevent struct member vars from shuffling around, can take address of struct now 2019-07-13 01:16:34 +02:00
Irmen de Jong a089c48378 finalize v 1.11 2019-07-12 20:31:18 +02:00
Irmen de Jong 3e5deda46c struct finished 2019-07-12 20:07:41 +02:00
Irmen de Jong 7500c6efd0 struct fixes 2019-07-12 17:57:56 +02:00
Irmen de Jong 717b5f3b07 struct fixes 2019-07-12 16:40:18 +02:00
Irmen de Jong 9f6fa60bf1 prepare 2019-07-12 14:38:37 +02:00
Irmen de Jong 1e9586f635 Structs can be compiled and executed in the vm! structs are just syntactic sugar for a set of variables for now. 2019-07-12 12:41:08 +02:00
Irmen de Jong 44f9d5e69e added struct syntax 2019-07-12 06:14:59 +02:00
Irmen de Jong 7c9b8f7d43 cleaned up some buildprocess scripts 2019-07-11 17:27:57 +02:00
Irmen de Jong 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
Irmen de Jong 3d7a4bf81a astvm can now more or less run all examples 2019-07-10 18:44:54 +02:00
Irmen de Jong d4b3e35bd2 astvm almost complete 2019-07-10 16:50:41 +02:00
Irmen de Jong a59f7c75dc fixed some compile time and vm arithmetic errors 2019-07-10 13:33:52 +02:00
Irmen de Jong 44fe2369d6 multitarget assignments removed 2019-07-10 10:11:37 +02:00
Irmen de Jong aaaab2cfcf fix asm gen for loops when dealing with registers as loopvar 2019-07-10 08:51:05 +02:00
Irmen de Jong 9a3dab20dc extra warnings about register usage in loops 2019-07-10 08:30:17 +02:00
Irmen de Jong 20379b5927 fixed astvm postincrdecr and rsave/rrestore 2019-07-10 08:13:42 +02:00
Irmen de Jong 34dcce67e4 fixed petscii conversion when printing text 2019-07-10 07:10:34 +02:00
Irmen de Jong 0c7f107d01 fix irq routine removal 2019-07-10 03:57:03 +02:00
Irmen de Jong 1f89571aa5 proper NOP removal 2019-07-10 03:06:31 +02:00
Irmen de Jong 7eed1ebbf8 optimized typecasting more 2019-07-10 02:54:39 +02:00
Irmen de Jong 12cb7d7abe optimize redundant typecasts more 2019-07-10 01:52:04 +02:00
Irmen de Jong c9b16dcbd9 nicer printing of arrays, fix inc/dec overflow issue in runtimevalue 2019-07-10 01:16:32 +02:00
Irmen de Jong dcab6d00bb ver 2019-07-10 00:50:18 +02:00
Irmen de Jong a85743f241 docs about 'when' statement 2019-07-10 00:45:53 +02:00
Irmen de Jong 14cabde5cf when statement extended with multiple choice values 2019-07-10 00:25:21 +02:00
Irmen de Jong cc078503e3 tehtriz example uses when statement 2019-07-09 23:39:03 +02:00
Irmen de Jong 2a0c3377f9 fixed Nop statements without parent 2019-07-09 23:27:09 +02:00
Irmen de Jong 16454f5560 optimized when asm 2019-07-09 21:59:50 +02:00
Irmen de Jong c1343a78f1 when working correctly in asm (corrected dup & cmp) 2019-07-09 21:41:47 +02:00
Irmen de Jong 9d0c65c682 when working correctly in stackvm and astvm 2019-07-09 20:39:08 +02:00
Irmen de Jong 9e6408244f fix scoping of variables in when statement 2019-07-09 19:44:59 +02:00
66 changed files with 2913 additions and 7680 deletions

No files matched your search

+5 -1
View File
@@ -21,6 +21,8 @@ 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
- structs to group together sets of variables and manipulate them at once
- 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
@@ -33,7 +35,9 @@ Rapid edit-compile-run-debug cycle:
- option to automatically run the program in the Vice emulator
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
- the compiler includes a virtual machine that can execute compiled code directy on the
host system without having to actually convert it to assembly to run on a real 6502.
This allows for very quick experimentation and debugging
It is mainly targeted at the Commodore-64 machine at this time.
Contributions to add support for other 8-bit (or other?!) machines are welcome.
-11
View File
@@ -76,17 +76,6 @@ artifacts {
archives shadowJar
}
task p8vmScript(type: CreateStartScripts) {
mainClassName = "prog8.vm.stackvm.MainKt"
applicationName = "p8vm"
outputDir = new File(project.buildDir, 'scripts')
classpath = jar.outputs.files + project.configurations.runtime
}
applicationDistribution.into("bin") {
from(p8vmScript)
fileMode = 0755
}
// To create a fat-jar use the 'create_compiler_jar' script for now
// @todo investigate https://imperceptiblethoughts.com/shadow/introduction/
+1 -1
View File
@@ -1 +1 @@
1.9
1.11
+20 -4
View File
@@ -1,9 +1,6 @@
package prog8.ast
import prog8.ast.base.FatalAstException
import prog8.ast.base.NameError
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.base.*
import prog8.ast.statements.Block
import prog8.ast.statements.Label
import prog8.ast.statements.Subroutine
@@ -74,6 +71,21 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
return builtinPlaceholder
}
if(scopedName.size>1) {
// a scoped name can a) refer to a member of a struct, or b) refer to a name in another module.
// try the struct first.
val thing = lookup(scopedName.dropLast(1), localContext) as? VarDecl
val struct = thing?.struct
if (struct != null) {
if(struct.statements.any { (it as VarDecl).name == scopedName.last()}) {
// return ref to the mangled name variable
val mangled = mangledStructMemberName(thing.name, scopedName.last())
val mangledVar = thing.definingScope().getLabelOrVariable(mangled)
return mangledVar
}
}
}
val stmt = localContext.definingModule().lookup(scopedName, localContext)
return when (stmt) {
is Label, is VarDecl, is Block, is Subroutine -> stmt
@@ -90,3 +102,7 @@ object BuiltinFunctionScopePlaceholder : INameScope {
override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {}
}
// prefix for struct member variables
internal fun mangledStructMemberName(varName: String, memberName: String) = "prog8struct_${varName}_$memberName"
+26 -3
View File
@@ -80,7 +80,7 @@ interface INameScope {
val subscopes = mutableMapOf<String, INameScope>()
for(stmt in statements) {
when(stmt) {
is INameScope -> subscopes[stmt.name] = stmt
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
is ForLoop -> subscopes[stmt.body.name] = stmt.body
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
@@ -94,16 +94,26 @@ interface INameScope {
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
}
is WhenStatement -> {
stmt.choices.forEach { subscopes[it.statements.name] = it.statements }
}
is INameScope -> subscopes[stmt.name] = stmt
}
}
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
if (stmt is AnonymousScope) {
val sub = stmt.getLabelOrVariable(name)
if(sub!=null)
return sub
}
}
return null
}
@@ -122,6 +132,19 @@ interface INameScope {
fun lookup(scopedName: List<String>, localContext: Node) : IStatement? {
if(scopedName.size>1) {
// a scoped name can a) refer to a member of a struct, or b) refer to a name in another module.
// try the struct first.
val thing = lookup(scopedName.dropLast(1), localContext) as? VarDecl
val struct = thing?.struct
if (struct != null) {
if(struct.statements.any { (it as VarDecl).name == scopedName.last()}) {
// return ref to the mangled name variable
val mangled = mangledStructMemberName(thing.name, scopedName.last())
val mangledVar = thing.definingScope().getLabelOrVariable(mangled)
return mangledVar
}
}
// it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program)
for(module in localContext.definingModule().program.modules) {
var scope: INameScope? = module
@@ -169,7 +192,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 {
+61 -39
View File
@@ -59,65 +59,64 @@ private fun prog8Parser.Statement_blockContext.toAst(): MutableList<IStatement>
private fun prog8Parser.StatementContext.toAst() : IStatement {
vardecl()?.let {
return VarDecl(VarDeclType.VAR,
it.datatype().toAst(),
it.ZEROPAGE() != null,
it.arrayindex()?.toAst(),
it.identifier().text,
null,
it.ARRAYSIG() != null || it.arrayindex() != null,
false,
it.toPosition())
}
vardecl()?.let { return it.toAst() }
varinitializer()?.let {
val vd = it.vardecl()
return VarDecl(VarDeclType.VAR,
vd.datatype().toAst(),
vd.ZEROPAGE() != null,
return VarDecl(
VarDeclType.VAR,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.identifier().text,
vd.varname.text,
vd.structname?.text,
it.expression().toAst(),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
it.toPosition())
it.toPosition()
)
}
constdecl()?.let {
val cvarinit = it.varinitializer()
val vd = cvarinit.vardecl()
return VarDecl(VarDeclType.CONST,
vd.datatype().toAst(),
vd.ZEROPAGE() != null,
return VarDecl(
VarDeclType.CONST,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.identifier().text,
vd.varname.text,
vd.structname?.text,
cvarinit.expression().toAst(),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
cvarinit.toPosition())
cvarinit.toPosition()
)
}
memoryvardecl()?.let {
val mvarinit = it.varinitializer()
val vd = mvarinit.vardecl()
return VarDecl(VarDeclType.MEMORY,
vd.datatype().toAst(),
vd.ZEROPAGE() != null,
return VarDecl(
VarDeclType.MEMORY,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.identifier().text,
vd.varname.text,
vd.structname?.text,
mvarinit.expression().toAst(),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
mvarinit.toPosition())
mvarinit.toPosition()
)
}
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())
@@ -175,12 +174,15 @@ private fun prog8Parser.StatementContext.toAst() : IStatement {
val whenstmt = whenstmt()?.toAst()
if(whenstmt!=null) return whenstmt
structdecl()?.let {
return StructDecl(it.identifier().text,
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
toPosition())
}
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()
@@ -218,8 +220,12 @@ private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
= asmsub_param().map {
AsmSubroutineParameter(it.vardecl().identifier().text, it.vardecl().datatype().toAst(),
it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition())
val vardecl = it.vardecl()
val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT
AsmSubroutineParameter(vardecl.varname.text, datatype,
it.registerorpair()?.toAst(),
it.statusregister()?.toAst(),
!it.stack?.text.isNullOrEmpty(), toPosition())
}
@@ -249,8 +255,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 {
@@ -285,7 +290,8 @@ private fun prog8Parser.Sub_return_partContext.toAst(): List<DataType> {
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
vardecl().map {
SubroutineParameter(it.identifier().text, it.datatype().toAst(), it.toPosition())
val datatype = it.datatype()?.toAst() ?: DataType.STRUCT
SubroutineParameter(it.varname.text, datatype, it.toPosition())
}
@@ -513,7 +519,7 @@ private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
val loopregister = register()?.toAst()
val datatype = datatype()?.toAst()
val zeropage = ZEROPAGE()!=null
val zeropage = if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE
val loopvar = identifier()?.toAst()
val iterable = expression()!!.toAst()
val scope =
@@ -554,13 +560,28 @@ 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())
}
private fun prog8Parser.VardeclContext.toAst(): VarDecl {
return VarDecl(
VarDeclType.VAR,
datatype()?.toAst() ?: DataType.STRUCT,
if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
arrayindex()?.toAst(),
varname.text,
structname?.text,
null,
ARRAYSIG() != null || arrayindex() != null,
false,
toPosition()
)
}
internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
@@ -588,3 +609,4 @@ internal fun unescape(str: String, position: Position): String {
}
return result.joinToString("")
}
+33 -23
View File
@@ -5,18 +5,19 @@ import prog8.ast.Node
/**************************** AST Data classes ****************************/
enum class DataType {
UBYTE,
BYTE,
UWORD,
WORD,
FLOAT,
STR,
STR_S,
ARRAY_UB,
ARRAY_B,
ARRAY_UW,
ARRAY_W,
ARRAY_F;
UBYTE, // pass by value
BYTE, // pass by value
UWORD, // pass by value
WORD, // pass by value
FLOAT, // pass by value
STR, // pass by reference
STR_S, // pass by reference
ARRAY_UB, // pass by reference
ARRAY_B, // pass by reference
ARRAY_UW, // pass by reference
ARRAY_W, // pass by reference
ARRAY_F, // pass by reference
STRUCT; // pass by reference
/**
* is the type assignable to the given other type?
@@ -24,26 +25,33 @@ enum class DataType {
infix fun isAssignableTo(targetType: DataType) =
// what types are assignable to others without loss of precision?
when(this) {
UBYTE -> targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT
BYTE -> targetType == BYTE || targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT
UWORD -> targetType == UWORD || targetType == FLOAT
WORD -> targetType == WORD || targetType==UWORD || targetType == FLOAT
UBYTE -> targetType in setOf(UBYTE, UWORD, WORD, FLOAT)
BYTE -> targetType in setOf(BYTE, UBYTE, UWORD, WORD, FLOAT)
UWORD -> targetType in setOf(UWORD, FLOAT)
WORD -> targetType in setOf(WORD, UWORD, FLOAT)
FLOAT -> targetType == FLOAT
STR -> targetType == STR || targetType==STR_S
STR_S -> targetType == STR || targetType==STR_S
in ArrayDatatypes -> targetType === this
in ArrayDatatypes -> targetType == this
else -> false
}
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 {
@@ -89,17 +97,19 @@ enum class VarDeclType {
MEMORY
}
val IterableDatatypes = setOf(
DataType.STR, DataType.STR_S,
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W,
DataType.ARRAY_F)
val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE)
val WordDatatypes = setOf(DataType.UWORD, DataType.WORD)
val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
val StringDatatypes = setOf(DataType.STR, DataType.STR_S)
val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
val IterableDatatypes = setOf(
DataType.STR, DataType.STR_S,
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W,
DataType.ARRAY_F)
val PassByValueDatatypes = NumericDatatypes
val PassByReferenceDatatypes = IterableDatatypes.plus(DataType.STRUCT)
val ArrayElementTypes = mapOf(
DataType.ARRAY_B to DataType.BYTE,
DataType.ARRAY_UB to DataType.UBYTE,
@@ -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.FlattenAnonymousScopesAndRemoveNops
// 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.removeNopsFlattenAnonScopes() {
val flattener = FlattenAnonymousScopesAndRemoveNops()
flattener.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)
@@ -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)
@@ -231,11 +231,7 @@ class ArrayIndexedExpression(val identifier: IdentifierReference,
return when (target.datatype) {
in NumericDatatypes -> null
in StringDatatypes -> DataType.UBYTE
DataType.ARRAY_UB -> DataType.UBYTE
DataType.ARRAY_B -> DataType.BYTE
DataType.ARRAY_UW -> DataType.UWORD
DataType.ARRAY_W -> DataType.WORD
DataType.ARRAY_F -> DataType.FLOAT
in ArrayDatatypes -> ArrayElementTypes[target.datatype]
else -> throw FatalAstException("invalid dt")
}
}
@@ -257,7 +253,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 +277,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 +293,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 +312,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 +420,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 +514,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 +579,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 +648,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 +690,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 +756,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)
+167 -71
View File
@@ -7,8 +7,8 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.CompilationOptions
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.functions.BuiltinFunctions
import java.io.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,30 @@ 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))
}
}
}
val sourceIdent = assignment.value as? IdentifierReference
val targetIdent = assignment.target.identifier
if(sourceIdent!=null && targetIdent!=null) {
val sourceVar = sourceIdent.targetVarDecl(program.namespace)
val targetVar = targetIdent.targetVarDecl(program.namespace)
if(sourceVar?.struct!=null && targetVar?.struct!=null) {
if(sourceVar.struct!==targetVar.struct)
checkResult.add(ExpressionError("assignment of different struct types", assignment.position))
}
}
var resultingAssignment = assignment
for (target in assignment.targets) {
resultingAssignment = processAssignmentTarget(resultingAssignment, target)
}
resultingAssignment = processAssignmentTarget(resultingAssignment, assignment.target)
return super.visit(resultingAssignment)
}
@@ -392,7 +408,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
}
@@ -401,28 +417,29 @@ internal class AstChecker(private val program: Program,
if(targetDatatype!=null) {
val constVal = assignment.value.constValue(program)
if(constVal!=null) {
val arrayspec = if(target.identifier!=null) {
val targetVar = program.namespace.lookup(target.identifier.nameInSource, assignment) as? VarDecl
targetVar?.arraysize
} else null
checkValueTypeAndRange(targetDatatype,
val targetVar =
if(target.identifier!=null)
program.namespace.lookup(target.identifier.nameInSource, assignment) as? VarDecl
else
null
val arrayspec = if(target.identifier!=null) targetVar?.arraysize else null
checkValueTypeAndRange(targetDatatype, targetVar?.struct,
arrayspec ?: ArrayIndex(LiteralValue.optimalInteger(-1, assignment.position), assignment.position),
constVal, program.heap)
} 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, target, sourceDatatype, assignment.value, assignment.position)
}
else
checkAssignmentCompatible(targetDatatype, sourceDatatype, assignment.value, assignment.targets, assignment.position)
}
}
return assignment
@@ -433,8 +450,8 @@ internal class AstChecker(private val program: Program,
if(variable==null)
checkResult.add(ExpressionError("pointer-of operand must be the name of a heap variable", addressOf.position))
else {
if(variable.datatype !in ArrayDatatypes && variable.datatype !in StringDatatypes)
checkResult.add(ExpressionError("pointer-of operand must be the name of a string or array heap variable", addressOf.position))
if(variable.datatype !in ArrayDatatypes && variable.datatype !in StringDatatypes && variable.datatype!=DataType.STRUCT)
checkResult.add(ExpressionError("invalid pointer-of operand type", addressOf.position))
}
if(addressOf.scopedname==null)
throw FatalAstException("the scopedname of AddressOf should have been set by now $addressOf")
@@ -450,7 +467,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")
}
@@ -483,6 +500,16 @@ internal class AstChecker(private val program: Program,
when(decl.type) {
VarDeclType.VAR, VarDeclType.CONST -> {
if(decl.datatype==DataType.STRUCT) {
if(decl.struct==null)
throw FatalAstException("struct vardecl should be linked to its struct $decl")
if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE || decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
err("struct can not be in zeropage")
}
if(decl.struct!=null) {
if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE || decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
err("struct can not be in zeropage")
}
if (decl.value == null) {
when {
decl.datatype in NumericDatatypes -> {
@@ -496,6 +523,9 @@ internal class AstChecker(private val program: Program,
litVal.parent = decl
decl.value = litVal
}
decl.datatype == DataType.STRUCT -> {
// TODO structs are not initialized with a literal value yet, should be an array of zeros!
}
decl.type== VarDeclType.VAR -> {
val litVal = LiteralValue(decl.datatype, initHeapId = heapStringSentinel, position = decl.position) // point to the sentinel heap value instead
litVal.parent=decl
@@ -518,7 +548,7 @@ internal class AstChecker(private val program: Program,
else
ArrayIndex(LiteralValue.optimalInteger(-2, decl.position), decl.position)
)
checkValueTypeAndRange(decl.datatype, arraySpec, decl.value as LiteralValue, program.heap)
checkValueTypeAndRange(decl.datatype, decl.struct, arraySpec, decl.value as LiteralValue, program.heap)
}
else -> {
err("var/const declaration needs a compile-time constant initializer value, or range, instead found: ${decl.value!!.javaClass.simpleName}")
@@ -651,7 +681,7 @@ internal class AstChecker(private val program: Program,
ArrayIndex.forArray(literalValue, program.heap)
else
ArrayIndex(LiteralValue.optimalInteger(-3, literalValue.position), literalValue.position)
checkValueTypeAndRange(literalValue.type, arrayspec, literalValue, program.heap)
checkValueTypeAndRange(literalValue.type, null, arrayspec, literalValue, program.heap)
val lv = super.visit(literalValue)
when(lv.type) {
@@ -782,8 +812,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)
}
@@ -824,8 +858,8 @@ internal class AstChecker(private val program: Program,
for (arg in args.withIndex().zip(target.parameters)) {
val argDt = arg.first.value.inferType(program)
if(argDt!=null && !(argDt isAssignableTo arg.second.type)) {
// for asm subroutines having STR param it's okay to provide a UWORD too (pointer value)
if(!(target.isAsmSubroutine && arg.second.type in StringDatatypes && argDt== DataType.UWORD))
// for asm subroutines having STR param it's okay to provide a UWORD (address value)
if(!(target.isAsmSubroutine && arg.second.type in StringDatatypes && argDt == DataType.UWORD))
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position))
}
@@ -926,14 +960,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)
@@ -987,7 +1026,8 @@ internal class AstChecker(private val program: Program,
}
}
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArrayIndex, value: LiteralValue, heap: HeapValues) : Boolean {
private fun checkValueTypeAndRange(targetDt: DataType, struct: StructDecl?,
arrayspec: ArrayIndex, value: LiteralValue, heap: HeapValues) : Boolean {
fun err(msg: String) : Boolean {
checkResult.add(ExpressionError(msg, value.position))
return false
@@ -1115,6 +1155,22 @@ internal class AstChecker(private val program: Program,
}
return err("invalid float array initialization value ${value.type}, expected $targetDt")
}
DataType.STRUCT -> {
if(value.type in ArrayDatatypes) {
if(value.arrayvalue!!.size != struct!!.numberOfElements)
return err("number of values is not the same as the number of members in the struct")
for(elt in value.arrayvalue.zip(struct.statements)) {
val vardecl = elt.second as VarDecl
val valuetype = elt.first.inferType(program)!!
if (!(valuetype isAssignableTo vardecl.datatype)) {
checkResult.add(ExpressionError("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position))
return false
}
}
return true
}
return false
}
}
return true
}
@@ -1169,13 +1225,13 @@ internal class AstChecker(private val program: Program,
}
private fun checkAssignmentCompatible(targetDatatype: DataType,
target: AssignTarget,
sourceDatatype: DataType,
sourceValue: IExpression,
assignTargets: List<AssignTarget>,
position: Position) : Boolean {
if(sourceValue is RangeExpr)
checkResult.add(SyntaxError("can't assign a range value", position))
checkResult.add(SyntaxError("can't assign a range value to something else", position))
val result = when(targetDatatype) {
DataType.BYTE -> sourceDatatype== DataType.BYTE
@@ -1185,6 +1241,24 @@ internal class AstChecker(private val program: Program,
DataType.FLOAT -> sourceDatatype in NumericDatatypes
DataType.STR -> sourceDatatype== DataType.STR
DataType.STR_S -> sourceDatatype== DataType.STR_S
DataType.STRUCT -> {
// for now we've decided you cannot assign struct by-value.
// but you can however assign an array to it of the correct size
if(sourceDatatype in ArrayDatatypes) {
val identifier = sourceValue as IdentifierReference
val sourceArraySize = identifier.targetVarDecl(program.namespace)!!.arraysize?.size()
val targetstruct = target.identifier!!.targetVarDecl(program.namespace)!!.struct!!
return targetstruct.numberOfElements == sourceArraySize
}
// if(sourceDatatype==DataType.STRUCT) {
// val sourcename = (sourceValue as IdentifierReference).nameInSource
// val vd1 = program.namespace.lookup(sourcename, target) as? VarDecl
// val targetname = target.identifier!!.nameInSource
// val vd2 = program.namespace.lookup(targetname, target) as? VarDecl
// return vd1?.struct == vd2?.struct
// }
false
}
else -> checkResult.add(SyntaxError("cannot assign new value to variable of type $targetDatatype", position))
}
@@ -1192,16 +1266,38 @@ 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))
else
checkResult.add(ExpressionError("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position))
else {
if(targetDatatype==DataType.UWORD && sourceDatatype in PassByReferenceDatatypes)
checkResult.add(ExpressionError("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}, perhaps forgot '&' ?", position))
else
checkResult.add(ExpressionError("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position))
}
return false
}
override fun visit(structDecl: StructDecl): IStatement {
// a struct can only contain 1 or more vardecls and can not be nested
if(structDecl.statements.isEmpty())
checkResult.add(SyntaxError("struct must contain at least one member", structDecl.position))
for(member in structDecl.statements){
val decl = member as? VarDecl
if(decl==null)
checkResult.add(SyntaxError("struct can only contain variable declarations", structDecl.position))
else {
if(decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)
checkResult.add(SyntaxError("struct can not contain zeropage members", decl.position))
if(decl.datatype !in NumericDatatypes)
checkResult.add(SyntaxError("structs can only contain numerical types", decl.position))
}
}
return structDecl
}
}
@@ -11,7 +11,8 @@ import prog8.functions.BuiltinFunctions
internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstModifyingVisitor {
private val checkResult: MutableList<AstException> = mutableListOf()
private var blocks: MutableMap<String, Block> = mutableMapOf()
private var blocks = mutableMapOf<String, Block>()
internal val anonymousVariablesFromHeap = mutableMapOf<String, Pair<LiteralValue, VarDecl>>()
internal fun result(): List<AstException> {
return checkResult
@@ -55,6 +56,22 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", decl.position))
// is it a struct variable? then define all its struct members as mangled names,
// and include the original decl as well.
if(decl.datatype==DataType.STRUCT) {
if(decl.structHasBeenFlattened)
return decl // don't do this multiple times
if(decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes})
return decl // a non-numeric member, not supported. proper error is given by AstChecker later
val decls = decl.flattenStructMembers()
decls.add(decl)
val result = AnonymousScope(decls, decl.position)
result.linkParents(decl.parent)
return result
}
val existing = namespace.lookup(listOf(decl.name), decl)
if (existing != null && existing !== decl)
nameError(decl.name, decl.position, existing)
@@ -98,8 +115,8 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
subroutine.parameters
.filter { it.name !in namesInSub }
.forEach {
val vardecl = VarDecl(VarDeclType.VAR, it.type, false, null, it.name, null,
isArray = false, autoGenerated = true, position = subroutine.position)
val vardecl = VarDecl(VarDeclType.VAR, it.type, ZeropageWish.DONTCARE, null, it.name, null, null,
isArray = false, hiddenButDoNotRemove = true, position = subroutine.position)
vardecl.linkParents(subroutine)
subroutine.statements.add(0, vardecl)
}
@@ -137,8 +154,8 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first())
if(existing==null) {
// create the local scoped for loop variable itself
val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, forLoop.zeropage, null, varName, null,
isArray = false, autoGenerated = true, position = forLoop.loopVar.position)
val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, forLoop.zeropage, null, varName, null, null,
isArray = false, hiddenButDoNotRemove = true, position = forLoop.loopVar.position)
vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl)
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
@@ -150,8 +167,8 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(ForLoop.iteratorLoopcounterVarname), forLoop.body.statements.first())
if(existing==null) {
// create loop iteration counter variable (without value, to avoid an assignment)
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, true, null, ForLoop.iteratorLoopcounterVarname, null,
isArray = false, autoGenerated = true, position = forLoop.loopVar.position)
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE, null, ForLoop.iteratorLoopcounterVarname, null, null,
isArray = false, hiddenButDoNotRemove = true, position = forLoop.loopVar.position)
vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl)
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
@@ -168,39 +185,41 @@ 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)
}
internal val anonymousVariablesFromHeap = mutableMapOf<String, Pair<LiteralValue, VarDecl>>()
override fun visit(literalValue: LiteralValue): LiteralValue {
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!
val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, "$autoHeapValuePrefix${literalValue.heapId}", literalValue,
isArray = false, autoGenerated = false, position = literalValue.position)
// (note: ususally, this has been taken care of already when the var was created)
val declaredType = if(literalValue.isArray) ArrayElementTypes.getValue(literalValue.type) else literalValue.type
val variable = VarDecl(VarDeclType.VAR,
declaredType,
ZeropageWish.NOT_IN_ZEROPAGE,
null,
"$autoHeapValuePrefix${literalValue.heapId}",
null,
literalValue,
isArray = literalValue.isArray, hiddenButDoNotRemove = true, position = literalValue.position)
anonymousVariablesFromHeap[variable.name] = Pair(literalValue, variable)
}
return super.visit(literalValue)
@@ -213,4 +232,14 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
return super.visit(addressOf)
}
override fun visit(structDecl: StructDecl): IStatement {
for(member in structDecl.statements){
val decl = member as? VarDecl
if(decl!=null && decl.datatype !in NumericDatatypes)
checkResult.add(SyntaxError("structs can only contain numerical types", decl.position))
}
return super.visit(structDecl)
}
}
@@ -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,12 @@ interface IAstModifyingVisitor {
}
fun visit(whenChoice: WhenChoice) {
whenChoice.value?.accept(this)
whenChoice.values?.forEach { it.accept(this) }
whenChoice.statements.accept(this)
}
fun visit(structDecl: StructDecl): IStatement {
structDecl.statements = structDecl.statements.map{ it.accept(this) }.toMutableList()
return structDecl
}
}
@@ -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,11 @@ interface IAstVisitor {
}
fun visit(whenChoice: WhenChoice) {
whenChoice.value?.accept(this)
whenChoice.values?.forEach { it.accept(this) }
whenChoice.statements.accept(this)
}
fun visit(structDecl: StructDecl) {
structDecl.statements.forEach { it.accept(this) }
}
}
@@ -2,16 +2,58 @@ package prog8.ast.processing
import kotlin.comparisons.nullsLast
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.*
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
fun flattenStructAssignment(structAssignment: Assignment, program: Program): List<Assignment> {
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!!
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
if(!sourceVar.isArray && sourceVar.struct==null)
throw FatalAstException("can only assign arrays or structs to structs")
if(sourceVar.isArray) {
val sourceArray = (sourceVar.value as LiteralValue).arrayvalue!!
return struct.statements.zip(sourceArray).map { member ->
val decl = member.first as VarDecl
val mangled = mangledStructMemberName(identifierName, decl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
null, member.second, member.second.position)
assign.linkParents(structAssignment)
assign
}
}
else {
// struct memberwise copy
val sourceStruct = sourceVar.struct!!
if(sourceStruct!==targetVar.struct) {
// structs are not the same in assignment
return listOf() // error will be printed elsewhere
}
return struct.statements.zip(sourceStruct.statements).map { member ->
val targetDecl = member.first as VarDecl
val sourceDecl = member.second as VarDecl
if(targetDecl.name != sourceDecl.name)
throw FatalAstException("struct member mismatch")
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
null, sourceIdref, member.second.position)
assign.linkParents(structAssignment)
assign
}
}
}
internal class StatementReorderer(private val program: Program): IAstModifyingVisitor {
// Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set.
@@ -91,7 +133,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 +181,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 +215,32 @@ 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) {
// 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) {
if(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
}
} else TODO("multi-target assign")
}
// struct assignments will be flattened
if(valuetype==DataType.STRUCT && targettype==DataType.STRUCT) {
val assignments = flattenStructAssignment(assignment, program)
if(assignments.isEmpty()) {
// something went wrong (probably incompatible struct types)
// we'll get an error later from the AstChecker
return assignment
} else {
val scope = AnonymousScope(assignments.toMutableList(), assignment.position)
scope.linkParents(assignment.parent)
return scope
}
}
return super.visit(assignment)
}
@@ -264,7 +319,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 +332,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)
}
}
@@ -8,6 +8,8 @@ import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.LiteralValue
import prog8.ast.statements.*
import prog8.compiler.CompilerException
internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope): IAstModifyingVisitor {
// For VarDecls that declare an initialization value:
@@ -21,18 +23,17 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
// Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc).
private val vardeclsToAdd = mutableMapOf<INameScope, MutableMap<String, VarDecl>>()
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
override fun visit(module: Module) {
vardeclsToAdd.clear()
super.visit(module)
// add any new vardecls to the various scopes
for(decl in vardeclsToAdd)
for(d in decl.value) {
d.value.linkParents(decl.key as Node)
decl.key.statements.add(0, d.value)
}
for((where, decls) in vardeclsToAdd) {
where.statements.addAll(0, decls)
decls.forEach { it.linkParents(where as Node) }
}
}
override fun visit(decl: VarDecl): IStatement {
@@ -83,7 +84,7 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList<IExpression>, parent: IStatement) {
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
for(argparam in subroutine.parameters.withIndex().zip(arglist)) {
if(argparam.first.value.type== DataType.UWORD || argparam.first.value.type in StringDatatypes) {
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type in StringDatatypes) {
if(argparam.second is AddressOf)
continue
val idref = argparam.second as? IdentifierReference
@@ -107,10 +108,9 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
// add a vardecl so that the autovar can be resolved in later lookups
val variable = VarDecl(VarDeclType.VAR, strvalue.type, false, null, autoVarName, strvalue,
isArray = false, autoGenerated = false, position = strvalue.position)
val variable = VarDecl(VarDeclType.VAR, strvalue.type, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, strvalue,
isArray = false, hiddenButDoNotRemove = false, position = strvalue.position)
addVarDecl(strvalue.definingScope(), variable)
// println("MADE ANONVAR $variable") // XXX
}
}
}
@@ -119,8 +119,10 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
private fun addVarDecl(scope: INameScope, variable: VarDecl) {
if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableMapOf()
vardeclsToAdd.getValue(scope)[variable.name]=variable
vardeclsToAdd[scope] = mutableListOf()
val declList = vardeclsToAdd.getValue(scope)
if(declList.all{it.name!=variable.name})
declList.add(variable)
}
}
@@ -6,8 +6,7 @@ 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
import prog8.compiler.target.c64.MachineDefinition
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : IStatement {
@@ -86,24 +85,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)
@@ -136,16 +135,30 @@ class Break(override val position: Position) : IStatement {
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
}
enum class ZeropageWish {
REQUIRE_ZEROPAGE,
PREFER_ZEROPAGE,
DONTCARE,
NOT_IN_ZEROPAGE
}
class VarDecl(val type: VarDeclType,
private val declaredDatatype: DataType,
val zeropage: Boolean,
val zeropage: ZeropageWish,
var arraysize: ArrayIndex?,
val name: String,
private val structName: String?,
var value: IExpression?,
val isArray: Boolean,
val autoGenerated: Boolean,
val hiddenButDoNotRemove: Boolean,
override val position: Position) : IStatement {
override lateinit var parent: Node
var struct: StructDecl? = null // set later (because at parse time, we only know the name)
private set
var structHasBeenFlattened = false // set later
private set
override val expensiveToInline
get() = value!=null && value !is LiteralValue
@@ -160,7 +173,7 @@ class VarDecl(val type: VarDeclType,
DataType.FLOAT -> DataType.ARRAY_F
else -> {
datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats", position))
DataType.UBYTE
DataType.ARRAY_UB
}
}
@@ -168,6 +181,9 @@ class VarDecl(val type: VarDeclType,
this.parent = parent
arraysize?.linkParents(this)
value?.linkParents(this)
if(structName!=null) {
struct = definingScope().lookup(listOf(structName), this) as StructDecl
}
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -176,7 +192,7 @@ class VarDecl(val type: VarDeclType,
val scopedname: String by lazy { makeScopedName(name) }
override fun toString(): String {
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, array=$isArray, value=$value, pos=$position)"
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, struct=$structName, value=$value, pos=$position)"
}
fun asDefaultValueDecl(parent: Node?): VarDecl {
@@ -188,11 +204,32 @@ class VarDecl(val type: VarDeclType,
DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = 0.0, position = position)
else -> throw FatalAstException("can only set a default value for a numeric type")
}
val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, constValue, isArray, true, position)
val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, structName, constValue, isArray, true, position)
if(parent!=null)
decl.linkParents(parent)
return decl
}
fun flattenStructMembers(): MutableList<IStatement> {
val result = struct!!.statements.withIndex().map {
val member = it.value as VarDecl
val initvalue = if(value!=null) (value as LiteralValue).arrayvalue!![it.index] else null
VarDecl(
VarDeclType.VAR,
member.datatype,
ZeropageWish.NOT_IN_ZEROPAGE,
member.arraysize,
mangledStructMemberName(name, member.name),
struct!!.name,
initvalue,
member.isArray,
true,
member.position
) as IStatement
}.toMutableList()
structHasBeenFlattened = true
return result
}
}
class ArrayIndex(var index: IExpression, override val position: Position) : Node {
@@ -224,14 +261,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 +276,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 +511,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,
@@ -608,7 +648,7 @@ class BranchStatement(var condition: BranchCondition,
class ForLoop(val loopRegister: Register?,
val decltype: DataType?,
val zeropage: Boolean,
val zeropage: ZeropageWish,
val loopVar: IdentifierReference?,
var iterable: IExpression,
var body: AnonymousScope,
@@ -679,35 +719,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)
@@ -715,6 +760,38 @@ class WhenChoice(val value: IExpression?, // if null, this is the 'el
}
class StructDecl(override val name: String,
override var statements: MutableList<IStatement>, // actually, only vardecls here
override val position: Position): IStatement, INameScope {
override lateinit var parent: Node
override val expensiveToInline: Boolean = true
override fun linkParents(parent: Node) {
this.parent = parent
this.statements.forEach { it.linkParents(this) }
}
val numberOfElements: Int
get() = this.statements.size
val memorySize: Int
get() = this.statements.map {
val decl = it as VarDecl
when {
decl.datatype in ByteDatatypes -> 8
decl.datatype in WordDatatypes -> 16
decl.datatype==DataType.FLOAT -> MachineDefinition.Mflpt5.MemorySize
decl.datatype in StringDatatypes -> TODO("stringvalue size")
decl.datatype in ArrayDatatypes -> decl.arraysize!!.size()!!
decl.datatype==DataType.STRUCT -> decl.struct!!.memorySize
else -> throw FatalAstException("can't get size for $decl")
}
}.sum()
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
}
class DirectMemoryWrite(var addressExpression: IExpression, override val position: Position) : Node {
override lateinit var parent: Node
+39 -20
View File
@@ -85,21 +85,30 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
DataType.ARRAY_UW -> "uword["
DataType.ARRAY_W -> "word["
DataType.ARRAY_F -> "float["
else -> "?????"
DataType.STRUCT -> "" // the name of the struct is enough
else -> "?????2"
}
}
override fun visit(decl: VarDecl) {
if(decl.autoGenerated) {
// skip autogenerated vardecl
return
override fun visit(structDecl: StructDecl) {
outputln("struct ${structDecl.name} {")
scopelevel++
for(decl in structDecl.statements) {
outputi("")
decl.accept(this)
output("\n")
}
scopelevel--
outputlni("}")
}
override fun visit(decl: VarDecl) {
when(decl.type) {
VarDeclType.VAR -> {}
VarDeclType.CONST -> output("const ")
VarDeclType.MEMORY -> output("&")
}
output(decl.struct?.name ?: "")
output(datatypeString(decl.datatype))
if(decl.arraysize!=null) {
decl.arraysize!!.index.accept(this)
@@ -107,7 +116,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
if(decl.isArray)
output("]")
if(decl.zeropage)
if(decl.zeropage == ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)
output(" @zp")
output(" ${decl.name} ")
if(decl.value!=null) {
@@ -126,7 +135,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
true==param.second.stack -> "stack"
param.second.registerOrPair!=null -> param.second.registerOrPair.toString()
param.second.statusflag!=null -> param.second.statusflag.toString()
else -> "?????"
else -> "?????1"
}
output("${datatypeString(param.first.type)} ${param.first.name} @$reg")
if(param.first!==subroutine.parameters.last())
@@ -169,8 +178,8 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
private fun outputStatements(statements: List<IStatement>) {
for(stmt in statements) {
if(stmt is VarDecl && stmt.autoGenerated)
continue // skip autogenerated decls
if(stmt is VarDecl && stmt.hiddenButDoNotRemove)
continue // skip autogenerated decls (to avoid generating a newline)
outputi("")
stmt.accept(this)
output("\n")
@@ -249,12 +258,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 +280,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
@@ -287,7 +305,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
output("for ")
if(forLoop.decltype!=null) {
output(datatypeString(forLoop.decltype))
if (forLoop.zeropage)
if (forLoop.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || forLoop.zeropage==ZeropageWish.PREFER_ZEROPAGE)
output(" @zp ")
else
output(" ")
@@ -318,11 +336,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 +365,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 +412,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 +430,6 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
outputln("")
}
override fun visit(nopStatement: NopStatement) {
TODO("NOP???")
output("; NOP @ ${nopStatement.position} $nopStatement")
}
}
+69 -65
View File
@@ -4,6 +4,7 @@ import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.base.RegisterOrPair.*
import prog8.ast.expressions.*
import prog8.ast.processing.flattenStructAssignment
import prog8.ast.statements.*
import prog8.compiler.intermediate.IntermediateProgram
import prog8.compiler.intermediate.Opcode
@@ -213,6 +214,7 @@ internal class Compiler(private val program: Program) {
is NopStatement -> {}
is InlineAssembly -> translate(stmt)
is WhenStatement -> translate(stmt)
is StructDecl -> {}
else -> TODO("translate statement $stmt to stackvm")
}
}
@@ -982,7 +984,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 +993,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 +1014,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 +1037,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 +1064,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 +1404,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()????
@@ -1451,6 +1446,11 @@ internal class Compiler(private val program: Program) {
else -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
}
}
DataType.STRUCT -> {
// Assume the value is an array. Flatten the struct assignment into memberwise assignments.
flattenStructAssignment(stmt, program).forEach { translate(it) }
return
}
in StringDatatypes -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
in ArrayDatatypes -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
else -> throw CompilerException("weird/unknown targetdt")
@@ -1461,8 +1461,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,18 +1488,20 @@ 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!!)
private fun pushStructAddress(value: IExpression) {
when (value) {
is LiteralValue -> throw CompilerException("can only push address of struct that is a variable on the heap")
is IdentifierReference -> {
// notice that the mangled name of the first struct member is the start address of this struct var
val vardecl = value.targetVarDecl(program.namespace)!!
val firstStructMember = (vardecl.struct!!.statements.first() as VarDecl).name
val firstVarName = listOf(vardecl.name, firstStructMember)
// find the flattened var that belongs to this first struct member
val firstVar = value.definingScope().lookup(firstVarName, value) as VarDecl
prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = firstVar.scopedname) // TODO
}
} else throw CompilerException("can only use multiple assignment targets on an asmsub call")
else -> throw CompilerException("can only take address of a the float as constant literal or variable")
}
}
private fun popValueIntoTarget(assignTarget: AssignTarget, datatype: DataType) {
@@ -1538,9 +1540,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 +1553,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 +1578,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 +1597,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 +1616,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 +1689,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 +1709,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 +1737,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 +1747,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 +1773,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 +1832,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)
@@ -2005,7 +2010,7 @@ internal class Compiler(private val program: Program) {
DataType.UBYTE -> when(sourceDt) {
DataType.UBYTE -> {}
DataType.BYTE -> prog.instr(Opcode.CAST_B_TO_UB)
DataType.UWORD-> prog.instr(Opcode.CAST_UW_TO_UB)
DataType.UWORD -> prog.instr(Opcode.CAST_UW_TO_UB)
DataType.WORD-> prog.instr(Opcode.CAST_W_TO_UB)
DataType.FLOAT -> prog.instr(Opcode.CAST_F_TO_UB)
else -> throw CompilerException("invalid cast $sourceDt to ${expr.type} -- should be an Ast check")
@@ -2077,6 +2082,9 @@ internal class Compiler(private val program: Program) {
else if(target.datatype== DataType.FLOAT) {
pushFloatAddress(addrof.identifier)
}
else if(target.datatype == DataType.STRUCT) {
pushStructAddress(addrof.identifier)
}
else
throw CompilerException("cannot take memory pointer $addrof")
}
@@ -2095,24 +2103,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 +2126,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)
+4 -2
View File
@@ -7,7 +7,7 @@ import prog8.ast.base.checkValid
import prog8.ast.base.reorderStatements
import prog8.ast.statements.Directive
import prog8.compiler.target.c64.AsmGen
import prog8.compiler.target.c64.C64Zeropage
import prog8.compiler.target.c64.MachineDefinition
import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements
import prog8.optimizer.simplifyExpressions
@@ -63,6 +63,7 @@ fun compileProgram(filepath: Path,
}
//println(" time2: $time2")
val time3 = measureTimeMillis {
programAst.removeNopsFlattenAnonScopes()
programAst.reorderStatements() // reorder statements and add type casts, to please the compiler later
}
//println(" time3: $time3")
@@ -84,6 +85,7 @@ fun compileProgram(filepath: Path,
}
}
programAst.removeNopsFlattenAnonScopes()
programAst.checkValid(compilerOptions) // check if final tree is valid
programAst.checkRecursion() // check if there are recursive subroutine calls
@@ -106,7 +108,7 @@ fun compileProgram(filepath: Path,
}
if (writeAssembly) {
val zeropage = C64Zeropage(compilerOptions)
val zeropage = MachineDefinition.C64Zeropage(compilerOptions)
intermediate.allocateZeropage(zeropage)
val assembly = AsmGen(compilerOptions, intermediate, programAst.heap, zeropage).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
@@ -4,7 +4,9 @@ import prog8.ast.antlr.escape
import prog8.ast.base.*
import prog8.ast.base.printWarning
import prog8.ast.expressions.LiteralValue
import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl
import prog8.ast.statements.ZeropageWish
import prog8.vm.RuntimeValue
import prog8.compiler.CompilerException
import prog8.compiler.HeapValues
@@ -16,10 +18,12 @@ import java.nio.file.Path
class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val source: Path) {
data class VariableParameters (val zp: ZeropageWish, val memberOfStruct: StructDecl?)
class ProgramBlock(val name: String,
var address: Int?,
val instructions: MutableList<Instruction> = mutableListOf(),
val variables: MutableMap<String, RuntimeValue> = mutableMapOf(), // names are fully scoped
val variables: MutableList<Triple<String, RuntimeValue, VariableParameters>> = mutableListOf(), // names are fully scoped
val memoryPointers: MutableMap<String, Pair<Int, DataType>> = mutableMapOf(),
val labels: MutableMap<String, Instruction> = mutableMapOf(), // names are fully scoped
val force_output: Boolean)
@@ -28,7 +32,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
get() { return variables.size }
val numInstructions: Int
get() { return instructions.filter { it.opcode!= Opcode.LINE }.size }
val variablesMarkedForZeropage: MutableSet<String> = mutableSetOf()
val variablesMarkedForZeropage: MutableSet<String> = mutableSetOf() // TODO maybe this can be removed now we have ValueParameters
}
val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
@@ -45,14 +49,16 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
// allocates all @zp marked variables on the zeropage (for all blocks, as long as there is space in the ZP)
var notAllocated = 0
for(block in blocks) {
val zpVariables = block.variables.filter { it.key in block.variablesMarkedForZeropage }
val zpVariables = block.variables.filter { it.first in block.variablesMarkedForZeropage }
if (zpVariables.isNotEmpty()) {
for (variable in zpVariables) {
for ((varname, value, varparams) in zpVariables) {
if(varparams.zp==ZeropageWish.NOT_IN_ZEROPAGE || varparams.memberOfStruct!=null)
throw CompilerException("zp conflict")
try {
val address = zeropage.allocate(variable.key, variable.value.type, null)
allocatedZeropageVariables[variable.key] = Pair(address, variable.value.type)
val address = zeropage.allocate(varname, value.type, null)
allocatedZeropageVariables[varname] = Pair(address, value.type)
} catch (x: ZeropageDepletedError) {
printWarning(x.toString() + " variable ${variable.key} type ${variable.value.type}")
printWarning(x.toString() + " variable $varname type ${value.type}")
notAllocated++
}
}
@@ -393,8 +399,16 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
fun variable(scopedname: String, decl: VarDecl) {
when(decl.type) {
VarDeclType.VAR -> {
// var decls that are defined inside of a StructDecl are skipped in the output
// because every occurrence of the members will have a separate mangled vardecl for that occurrence
if(decl.parent is StructDecl)
return
val valueparams = VariableParameters(decl.zeropage, decl.struct)
val value = when(decl.datatype) {
in NumericDatatypes -> RuntimeValue(decl.datatype, (decl.value as LiteralValue).asNumericValue!!)
in NumericDatatypes -> {
RuntimeValue(decl.datatype, (decl.value as LiteralValue).asNumericValue!!)
}
in StringDatatypes -> {
val litval = (decl.value as LiteralValue)
if(litval.heapId==null)
@@ -407,11 +421,17 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
throw CompilerException("array should already be in the heap")
RuntimeValue(decl.datatype, heapId = litval.heapId)
}
DataType.STRUCT -> {
// struct variables have been flattened already
return
}
else -> throw CompilerException("weird datatype")
}
currentBlock.variables[scopedname] = value
if(decl.zeropage)
currentBlock.variables.add(Triple(scopedname, value, valueparams))
if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)
currentBlock.variablesMarkedForZeropage.add(scopedname)
else if(decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
TODO("REQUIRE_ZEROPAGE not yet implemented")
}
VarDeclType.MEMORY -> {
// note that constants are all folded away, but assembly code may still refer to them
@@ -459,10 +479,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 +515,45 @@ 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 ((vname, value, parameters) in blk.variables) {
if(parameters.zp==ZeropageWish.REQUIRE_ZEROPAGE)
throw CompilerException("zp conflict")
val valuestr = value.toString()
val struct = if(parameters.memberOfStruct==null) "" else "struct=${parameters.memberOfStruct.name}"
out.println("$vname ${value.type.name.toLowerCase()} $valuestr zp=${parameters.zp} $struct")
}
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")
}
}
+110 -87
View File
@@ -6,6 +6,7 @@ package prog8.compiler.target.c64
import prog8.ast.antlr.escape
import prog8.ast.base.DataType
import prog8.ast.base.initvarsSubName
import prog8.ast.statements.ZeropageWish
import prog8.vm.RuntimeValue
import prog8.compiler.*
import prog8.compiler.intermediate.*
@@ -44,7 +45,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
// Convert invalid label names (such as "<anon-1>") to something that's allowed.
val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>()
for(block in program.blocks) {
val newvars = block.variables.map { symname(it.key, block) to it.value }.toMap().toMutableMap()
val newvars = block.variables.map { Triple(symname(it.first, block), it.second, it.third) }.toMutableList()
val newvarsZeropaged = block.variablesMarkedForZeropage.map{symname(it, block)}.toMutableSet()
val newlabels = block.labels.map { symname(it.key, block) to it.value}.toMap().toMutableMap()
val newinstructions = block.instructions.asSequence().map {
@@ -146,7 +147,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
return name.replace("-", "")
}
private fun makeFloatFill(flt: Mflpt5): String {
private fun makeFloatFill(flt: MachineDefinition.Mflpt5): String {
val b0 = "$"+flt.b0.toString(16).padStart(2, '0')
val b1 = "$"+flt.b1.toString(16).padStart(2, '0')
val b2 = "$"+flt.b2.toString(16).padStart(2, '0')
@@ -164,7 +165,8 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
out("\n.cpu '6502'\n.enc 'none'\n")
if(program.loadAddress==0) // fix load address
program.loadAddress = if(options.launcher==LauncherType.BASIC) BASIC_LOAD_ADDRESS else RAW_LOAD_ADDRESS
program.loadAddress = if(options.launcher==LauncherType.BASIC)
MachineDefinition.BASIC_LOAD_ADDRESS else MachineDefinition.RAW_LOAD_ADDRESS
when {
options.launcher == LauncherType.BASIC -> {
@@ -220,7 +222,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
// the global list of all floating point constants for the whole program
for(flt in globalFloatConsts) {
val floatFill = makeFloatFill(Mflpt5.fromNumber(flt.key))
val floatFill = makeFloatFill(MachineDefinition.Mflpt5.fromNumber(flt.key))
out("${flt.value}\t.byte $floatFill ; float ${flt.key}")
}
}
@@ -236,18 +238,20 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
}
// deal with zeropage variables
for(variable in blk.variables) {
val sym = symname(blk.name+"."+variable.key, null)
for((varname, value, parameters) in blk.variables) {
val sym = symname(blk.name+"."+varname, null)
val zpVar = program.allocatedZeropageVariables[sym]
if(zpVar==null) {
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
if(variable.value.type in zeropage.allowedDatatypes && variable.value.type != DataType.FLOAT) {
if(parameters.zp != ZeropageWish.NOT_IN_ZEROPAGE &&
value.type in zeropage.allowedDatatypes
&& value.type != DataType.FLOAT) {
try {
val address = zeropage.allocate(sym, variable.value.type, null)
out("${variable.key} = $address\t; auto zp ${variable.value.type}")
val address = zeropage.allocate(sym, value.type, null)
out("$varname = $address\t; auto zp ${value.type}")
// make sure we add the var to the set of zpvars for this block
blk.variablesMarkedForZeropage.add(variable.key)
program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type)
blk.variablesMarkedForZeropage.add(varname)
program.allocatedZeropageVariables[sym] = Pair(address, value.type)
} catch (x: ZeropageDepletedError) {
// leave it as it is.
}
@@ -255,7 +259,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
}
else {
// it was already allocated on the zp
out("${variable.key} = ${zpVar.first}\t; zp ${zpVar.second}")
out("$varname = ${zpVar.first}\t; zp ${zpVar.second}")
}
}
@@ -289,78 +293,97 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
}
private fun vardecls2asm(block: IntermediateProgram.ProgramBlock) {
// these are the non-zeropage variables
val sortedVars = block.variables.filter{it.key !in block.variablesMarkedForZeropage}.toList().sortedBy { it.second.type }
for (v in sortedVars) {
when (v.second.type) {
DataType.UBYTE -> out("${v.first}\t.byte 0")
DataType.BYTE -> out("${v.first}\t.char 0")
DataType.UWORD -> out("${v.first}\t.word 0")
DataType.WORD -> out("${v.first}\t.sint 0")
DataType.FLOAT -> out("${v.first}\t.byte 0,0,0,0,0 ; float")
DataType.STR, DataType.STR_S -> {
val rawStr = heap.get(v.second.heapId!!).str!!
val bytes = encodeStr(rawStr, v.second.type).map { "$" + it.toString(16).padStart(2, '0') }
out("${v.first}\t; ${v.second.type} \"${escape(rawStr).replace("\u0000", "<NULL>")}\"")
for (chunk in bytes.chunked(16))
out(" .byte " + chunk.joinToString())
}
DataType.ARRAY_UB -> {
// unsigned integer byte arraysize
val data = makeArrayFillDataUnsigned(v.second)
if (data.size <= 16)
out("${v.first}\t.byte ${data.joinToString()}")
else {
out(v.first)
for (chunk in data.chunked(16))
out(" .byte " + chunk.joinToString())
}
}
DataType.ARRAY_B -> {
// signed integer byte arraysize
val data = makeArrayFillDataSigned(v.second)
if (data.size <= 16)
out("${v.first}\t.char ${data.joinToString()}")
else {
out(v.first)
for (chunk in data.chunked(16))
out(" .char " + chunk.joinToString())
}
}
DataType.ARRAY_UW -> {
// unsigned word arraysize
val data = makeArrayFillDataUnsigned(v.second)
if (data.size <= 16)
out("${v.first}\t.word ${data.joinToString()}")
else {
out(v.first)
for (chunk in data.chunked(16))
out(" .word " + chunk.joinToString())
}
}
DataType.ARRAY_W -> {
// signed word arraysize
val data = makeArrayFillDataSigned(v.second)
if (data.size <= 16)
out("${v.first}\t.sint ${data.joinToString()}")
else {
out(v.first)
for (chunk in data.chunked(16))
out(" .sint " + chunk.joinToString())
}
}
DataType.ARRAY_F -> {
// float arraysize
val array = heap.get(v.second.heapId!!).doubleArray!!
val floatFills = array.map { makeFloatFill(Mflpt5.fromNumber(it)) }
out(v.first)
for(f in array.zip(floatFills))
out(" .byte ${f.second} ; float ${f.first}")
}
}
val uniqueNames = block.variables.map { it.first }.toSet()
if (uniqueNames.size != block.variables.size)
throw AssemblyError("not all variables have unique names")
// these are the non-zeropage variables.
// first get all the flattened struct members, they MUST remain in order
out("; flattened struct members")
val (structMembers, normalVars) = block.variables.partition { it.third.memberOfStruct!=null }
structMembers.forEach { vardecl2asm(it.first, it.second, it.third) }
// leave outsort the other variables by type
out("; other variables sorted by type")
val sortedVars = normalVars.sortedBy { it.second.type }
for ((varname, value, parameters) in sortedVars) {
if(varname in block.variablesMarkedForZeropage)
continue // skip the ones that belong in the zero page
vardecl2asm(varname, value, parameters)
}
}
private fun vardecl2asm(varname: String, value: RuntimeValue, parameters: IntermediateProgram.VariableParameters) {
when (value.type) {
DataType.UBYTE -> out("$varname\t.byte 0")
DataType.BYTE -> out("$varname\t.char 0")
DataType.UWORD -> out("$varname\t.word 0")
DataType.WORD -> out("$varname\t.sint 0")
DataType.FLOAT -> out("$varname\t.byte 0,0,0,0,0 ; float")
DataType.STR, DataType.STR_S -> {
val rawStr = heap.get(value.heapId!!).str!!
val bytes = encodeStr(rawStr, value.type).map { "$" + it.toString(16).padStart(2, '0') }
out("$varname\t; ${value.type} \"${escape(rawStr).replace("\u0000", "<NULL>")}\"")
for (chunk in bytes.chunked(16))
out(" .byte " + chunk.joinToString())
}
DataType.ARRAY_UB -> {
// unsigned integer byte arraysize
val data = makeArrayFillDataUnsigned(value)
if (data.size <= 16)
out("$varname\t.byte ${data.joinToString()}")
else {
out(varname)
for (chunk in data.chunked(16))
out(" .byte " + chunk.joinToString())
}
}
DataType.ARRAY_B -> {
// signed integer byte arraysize
val data = makeArrayFillDataSigned(value)
if (data.size <= 16)
out("$varname\t.char ${data.joinToString()}")
else {
out(varname)
for (chunk in data.chunked(16))
out(" .char " + chunk.joinToString())
}
}
DataType.ARRAY_UW -> {
// unsigned word arraysize
val data = makeArrayFillDataUnsigned(value)
if (data.size <= 16)
out("$varname\t.word ${data.joinToString()}")
else {
out(varname)
for (chunk in data.chunked(16))
out(" .word " + chunk.joinToString())
}
}
DataType.ARRAY_W -> {
// signed word arraysize
val data = makeArrayFillDataSigned(value)
if (data.size <= 16)
out("$varname\t.sint ${data.joinToString()}")
else {
out(varname)
for (chunk in data.chunked(16))
out(" .sint " + chunk.joinToString())
}
}
DataType.ARRAY_F -> {
// float arraysize
val array = heap.get(value.heapId!!).doubleArray!!
val floatFills = array.map { makeFloatFill(MachineDefinition.Mflpt5.fromNumber(it)) }
out(varname)
for(f in array.zip(floatFills))
out(" .byte ${f.second} ; float ${f.first}")
}
DataType.STRUCT -> throw AssemblyError("vars of type STRUCT should have been removed because flattened")
}
}
private fun encodeStr(str: String, dt: DataType): List<Short> {
return when(dt) {
DataType.STR -> {
@@ -554,14 +577,14 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_UW -> AsmFragment(" lda $variable+${index*2} | bne + | dec $variable+${index*2+1} |+ | dec $variable+${index*2}")
Opcode.INC_INDEXED_VAR_FLOAT -> AsmFragment(
"""
lda #<($variable+${index*Mflpt5.MemorySize})
ldy #>($variable+${index*Mflpt5.MemorySize})
lda #<($variable+${index* MachineDefinition.Mflpt5.MemorySize})
ldy #>($variable+${index* MachineDefinition.Mflpt5.MemorySize})
jsr c64flt.inc_var_f
""")
Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment(
"""
lda #<($variable+${index*Mflpt5.MemorySize})
ldy #>($variable+${index*Mflpt5.MemorySize})
lda #<($variable+${index* MachineDefinition.Mflpt5.MemorySize})
ldy #>($variable+${index* MachineDefinition.Mflpt5.MemorySize})
jsr c64flt.dec_var_f
""")
@@ -571,8 +594,8 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
private fun sameIndexedVarOperation(variable: String, indexVar: String, ins: Instruction): AsmFragment? {
// an in place operation that consists of a push-value / op / push-index-var / pop-into-indexed-var
val saveX = " stx ${C64Zeropage.SCRATCH_B1} |"
val restoreX = " | ldx ${C64Zeropage.SCRATCH_B1}"
val saveX = " stx ${MachineDefinition.C64Zeropage.SCRATCH_B1} |"
val restoreX = " | ldx ${MachineDefinition.C64Zeropage.SCRATCH_B1}"
val loadXWord: String
val loadX: String
@@ -1,6 +1,11 @@
package prog8.compiler.target.c64
import prog8.compiler.toHex
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
fun optimizeAssembly(lines: MutableList<String>): Int {
@@ -24,10 +29,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 +50,7 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
linesByFourteen = getLinesBy(lines, 14)
numberOfOptimizations++
}
@@ -44,15 +59,36 @@ 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_PLUS1_HEX,x" &&
lines[1].value.trim().startsWith("cmp ") &&
lines[2].value.trim().startsWith("beq ") &&
lines[3].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,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.
val removeLines = mutableListOf<Int>()
for(lines in linesByFour) {
if(lines[0].value.trim()=="sta ${ESTACK_LO.toHex()},x" &&
if(lines[0].value.trim()=="sta $ESTACK_LO_HEX,x" &&
lines[1].value.trim()=="dex" &&
lines[2].value.trim()=="inx" &&
lines[3].value.trim()=="lda ${ESTACK_LO.toHex()},x") {
lines[3].value.trim()=="lda $ESTACK_LO_HEX,x") {
removeLines.add(lines[0].index)
removeLines.add(lines[1].index)
removeLines.add(lines[2].index)
@@ -128,7 +164,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> {
@@ -3,8 +3,16 @@ package prog8.compiler.target.c64
import prog8.ast.base.printWarning
import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.Opcode
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
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 {
@@ -1368,7 +1376,7 @@ internal val patterns = listOf<AsmPattern>(
},
// floatvar = floatarray[index]
AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.READ_INDEXED_VAR_FLOAT, Opcode.POP_VAR_FLOAT)) { segment ->
val index = intVal(segment[0]) * Mflpt5.MemorySize
val index = intVal(segment[0]) * MachineDefinition.Mflpt5.MemorySize
"""
lda #<${segment[1].callLabel}+$index
ldy #>${segment[1].callLabel}+$index
@@ -1567,7 +1575,7 @@ internal val patterns = listOf<AsmPattern>(
},
// memfloat = floatarray[index]
AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.READ_INDEXED_VAR_FLOAT, Opcode.POP_MEM_FLOAT)) { segment ->
val index = intVal(segment[0]) * Mflpt5.MemorySize
val index = intVal(segment[0]) * MachineDefinition.Mflpt5.MemorySize
"""
lda #<${segment[1].callLabel}+$index
ldy #>${segment[1].callLabel}+$index
@@ -1581,7 +1589,7 @@ internal val patterns = listOf<AsmPattern>(
// floatarray[idxbyte] = float
AsmPattern(listOf(Opcode.PUSH_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment ->
val floatConst = getFloatConst(segment[0].arg!!)
val index = intVal(segment[1]) * Mflpt5.MemorySize
val index = intVal(segment[1]) * MachineDefinition.Mflpt5.MemorySize
"""
lda #<$floatConst
ldy #>$floatConst
@@ -1594,7 +1602,7 @@ internal val patterns = listOf<AsmPattern>(
},
// floatarray[idxbyte] = floatvar
AsmPattern(listOf(Opcode.PUSH_VAR_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment ->
val index = intVal(segment[1]) * Mflpt5.MemorySize
val index = intVal(segment[1]) * MachineDefinition.Mflpt5.MemorySize
"""
lda #<${segment[0].callLabel}
ldy #>${segment[0].callLabel}
@@ -1607,7 +1615,7 @@ internal val patterns = listOf<AsmPattern>(
},
// floatarray[idxbyte] = memfloat
AsmPattern(listOf(Opcode.PUSH_MEM_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment ->
val index = intVal(segment[1]) * Mflpt5.MemorySize
val index = intVal(segment[1]) * MachineDefinition.Mflpt5.MemorySize
"""
lda #<${hexVal(segment[0])}
ldy #>${hexVal(segment[0])}
@@ -1620,8 +1628,8 @@ internal val patterns = listOf<AsmPattern>(
},
// floatarray[idx2] = floatarray[idx1]
AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.READ_INDEXED_VAR_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment ->
val index1 = intVal(segment[0]) * Mflpt5.MemorySize
val index2 = intVal(segment[2]) * Mflpt5.MemorySize
val index1 = intVal(segment[0]) * MachineDefinition.Mflpt5.MemorySize
val index2 = intVal(segment[2]) * MachineDefinition.Mflpt5.MemorySize
"""
lda #<(${segment[1].callLabel}+$index1)
ldy #>(${segment[1].callLabel}+$index1)
@@ -1756,16 +1764,16 @@ internal val patterns = listOf<AsmPattern>(
// push word var as (u)byte
AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.CAST_W_TO_UB),
listOf(Opcode.PUSH_VAR_WORD, Opcode.CAST_W_TO_B)) { segment ->
" lda ${segment[0].callLabel} | sta ${ESTACK_LO.toHex()},x | dex "
" lda ${segment[0].callLabel} | sta $ESTACK_LO_HEX,x | dex "
},
// push uword var as (u)byte
AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.CAST_UW_TO_UB),
listOf(Opcode.PUSH_VAR_WORD, Opcode.CAST_UW_TO_B)) { segment ->
" lda ${segment[0].callLabel} | sta ${ESTACK_LO.toHex()},x | dex "
" lda ${segment[0].callLabel} | sta $ESTACK_LO_HEX,x | dex "
},
// push msb(word var)
AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.MSB)) { segment ->
" lda ${segment[0].callLabel}+1 | sta ${ESTACK_LO.toHex()},x | dex "
" lda ${segment[0].callLabel}+1 | sta $ESTACK_LO_HEX,x | dex "
},
// set a register pair to a certain memory address (of a variable)
@@ -1782,29 +1790,29 @@ internal val patterns = listOf<AsmPattern>(
// push memory byte | bytevalue
AsmPattern(listOf(Opcode.PUSH_MEM_B, Opcode.PUSH_BYTE, Opcode.BITOR_BYTE),
listOf(Opcode.PUSH_MEM_UB, Opcode.PUSH_BYTE, Opcode.BITOR_BYTE)) { segment ->
" lda ${hexVal(segment[0])} | ora #${hexVal(segment[1])} | sta ${ESTACK_LO.toHex()},x | dex "
" lda ${hexVal(segment[0])} | ora #${hexVal(segment[1])} | sta $ESTACK_LO_HEX,x | dex "
},
// push memory byte & bytevalue
AsmPattern(listOf(Opcode.PUSH_MEM_B, Opcode.PUSH_BYTE, Opcode.BITAND_BYTE),
listOf(Opcode.PUSH_MEM_UB, Opcode.PUSH_BYTE, Opcode.BITAND_BYTE)) { segment ->
" lda ${hexVal(segment[0])} | and #${hexVal(segment[1])} | sta ${ESTACK_LO.toHex()},x | dex "
" lda ${hexVal(segment[0])} | and #${hexVal(segment[1])} | sta $ESTACK_LO_HEX,x | dex "
},
// push memory byte ^ bytevalue
AsmPattern(listOf(Opcode.PUSH_MEM_B, Opcode.PUSH_BYTE, Opcode.BITXOR_BYTE),
listOf(Opcode.PUSH_MEM_UB, Opcode.PUSH_BYTE, Opcode.BITXOR_BYTE)) { segment ->
" lda ${hexVal(segment[0])} | eor #${hexVal(segment[1])} | sta ${ESTACK_LO.toHex()},x | dex "
" lda ${hexVal(segment[0])} | eor #${hexVal(segment[1])} | sta $ESTACK_LO_HEX,x | dex "
},
// push var byte | bytevalue
AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.PUSH_BYTE, Opcode.BITOR_BYTE)) { segment ->
" lda ${segment[0].callLabel} | ora #${hexVal(segment[1])} | sta ${ESTACK_LO.toHex()},x | dex "
" lda ${segment[0].callLabel} | ora #${hexVal(segment[1])} | sta $ESTACK_LO_HEX,x | dex "
},
// push var byte & bytevalue
AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.PUSH_BYTE, Opcode.BITAND_BYTE)) { segment ->
" lda ${segment[0].callLabel} | and #${hexVal(segment[1])} | sta ${ESTACK_LO.toHex()},x | dex "
" lda ${segment[0].callLabel} | and #${hexVal(segment[1])} | sta $ESTACK_LO_HEX,x | dex "
},
// push var byte ^ bytevalue
AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.PUSH_BYTE, Opcode.BITXOR_BYTE)) { segment ->
" lda ${segment[0].callLabel} | eor #${hexVal(segment[1])} | sta ${ESTACK_LO.toHex()},x | dex "
" lda ${segment[0].callLabel} | eor #${hexVal(segment[1])} | sta $ESTACK_LO_HEX,x | dex "
},
// push memory word | wordvalue
@@ -1813,10 +1821,10 @@ internal val patterns = listOf<AsmPattern>(
"""
lda ${hexVal(segment[0])}
ora #<${hexVal(segment[1])}
sta ${ESTACK_LO.toHex()},x
sta $ESTACK_LO_HEX,x
lda ${hexValPlusOne(segment[0])}
ora #>${hexVal(segment[1])}
sta ${ESTACK_HI.toHex()},x
sta $ESTACK_HI_HEX,x
dex
"""
},
@@ -1826,10 +1834,10 @@ internal val patterns = listOf<AsmPattern>(
"""
lda ${hexVal(segment[0])}
and #<${hexVal(segment[1])}
sta ${ESTACK_LO.toHex()},x
sta $ESTACK_LO_HEX,x
lda ${hexValPlusOne(segment[0])}
and #>${hexVal(segment[1])}
sta ${ESTACK_HI.toHex()},x
sta $ESTACK_HI_HEX,x
dex
"""
},
@@ -1839,10 +1847,10 @@ internal val patterns = listOf<AsmPattern>(
"""
lda ${hexVal(segment[0])}
eor #<${hexVal(segment[1])}
sta ${ESTACK_LO.toHex()},x
sta $ESTACK_LO_HEX,x
lda ${hexValPlusOne(segment[0])}
eor #>${hexVal(segment[1])}
sta ${ESTACK_HI.toHex()},x
sta $ESTACK_HI_HEX,x
dex
"""
},
@@ -1851,10 +1859,10 @@ internal val patterns = listOf<AsmPattern>(
"""
lda ${segment[0].callLabel}
ora #<${hexVal(segment[1])}
sta ${ESTACK_LO.toHex()},x
sta $ESTACK_LO_HEX,x
lda ${segment[0].callLabel}+1
ora #>${hexVal(segment[1])}
sta ${ESTACK_HI.toHex()},x
sta $ESTACK_HI_HEX,x
dex
"""
},
@@ -1863,10 +1871,10 @@ internal val patterns = listOf<AsmPattern>(
"""
lda ${segment[0].callLabel}
and #<${hexVal(segment[1])}
sta ${ESTACK_LO.toHex()},x
sta $ESTACK_LO_HEX,x
lda ${segment[0].callLabel}+1
and #>${hexVal(segment[1])}
sta ${ESTACK_HI.toHex()},x
sta $ESTACK_HI_HEX,x
dex
"""
},
@@ -1875,10 +1883,10 @@ internal val patterns = listOf<AsmPattern>(
"""
lda ${segment[0].callLabel}
eor #<${hexVal(segment[1])}
sta ${ESTACK_LO.toHex()},x
sta $ESTACK_LO_HEX,x
lda ${segment[0].callLabel}+1
eor #>${hexVal(segment[1])}
sta ${ESTACK_HI.toHex()},x
sta $ESTACK_HI_HEX,x
dex
"""
},
@@ -1971,27 +1979,27 @@ internal val patterns = listOf<AsmPattern>(
AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.PUSH_VAR_BYTE, Opcode.MKWORD)) { segment ->
"""
lda ${segment[0].callLabel}
sta ${ESTACK_LO.toHex()},x
sta $ESTACK_LO_HEX,x
lda ${segment[1].callLabel}
sta ${ESTACK_HI.toHex()},x
sta $ESTACK_HI_HEX,x
dex
"""
},
AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.PUSH_VAR_BYTE, Opcode.MKWORD)) { segment ->
"""
lda #${hexVal(segment[0])}
sta ${ESTACK_LO.toHex()},x
sta $ESTACK_LO_HEX,x
lda ${segment[1].callLabel}
sta ${ESTACK_HI.toHex()},x
sta $ESTACK_HI_HEX,x
dex
"""
},
AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.PUSH_BYTE, Opcode.MKWORD)) { segment ->
"""
lda ${segment[0].callLabel}
sta ${ESTACK_LO.toHex()},x
sta $ESTACK_LO_HEX,x
lda #${hexVal(segment[1])}
sta ${ESTACK_HI.toHex()},x
sta $ESTACK_HI_HEX,x
dex
"""
},
@@ -2032,28 +2040,28 @@ internal val patterns = listOf<AsmPattern>(
AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.ADD_B), listOf(Opcode.PUSH_BYTE, Opcode.ADD_UB)) { segment ->
val amount = segment[0].arg!!.integerValue()
if (amount in 1..2) {
" inc ${(ESTACK_LO + 1).toHex()},x | ".repeat(amount)
" inc $ESTACK_LO_PLUS1_HEX,x | ".repeat(amount)
} else
null
},
AsmPattern(listOf(Opcode.PUSH_WORD, Opcode.ADD_UW), listOf(Opcode.PUSH_WORD, Opcode.ADD_W)) { segment ->
val amount = segment[0].arg!!.integerValue()
if (amount in 1..2) {
" inc ${(ESTACK_LO + 1).toHex()},x | bne + | inc ${(ESTACK_HI + 1).toHex()},x |+ | ".repeat(amount)
" inc $ESTACK_LO_PLUS1_HEX,x | bne + | inc $ESTACK_HI_PLUS1_HEX,x |+ | ".repeat(amount)
} else
null
},
AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.SUB_B), listOf(Opcode.PUSH_BYTE, Opcode.SUB_UB)) { segment ->
val amount = segment[0].arg!!.integerValue()
if (amount in 1..2) {
" dec ${(ESTACK_LO + 1).toHex()},x | ".repeat(amount)
" dec $ESTACK_LO_PLUS1_HEX,x | ".repeat(amount)
} else
null
},
AsmPattern(listOf(Opcode.PUSH_WORD, Opcode.SUB_UW), listOf(Opcode.PUSH_WORD, Opcode.SUB_W)) { segment ->
val amount = segment[0].arg!!.integerValue()
if (amount in 1..2) {
" lda ${(ESTACK_LO + 1).toHex()},x | bne + | dec ${(ESTACK_HI + 1).toHex()},x |+ | dec ${(ESTACK_LO + 1).toHex()},x | ".repeat(amount)
" lda $ESTACK_LO_PLUS1_HEX,x | bne + | dec $ESTACK_HI_PLUS1_HEX,x |+ | dec $ESTACK_LO_PLUS1_HEX,x | ".repeat(amount)
} else
null
},
@@ -2063,7 +2071,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)
@@ -2093,14 +2114,14 @@ internal val patterns = listOf<AsmPattern>(
AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.MUL_B), listOf(Opcode.PUSH_BYTE, Opcode.MUL_UB)) { segment ->
val amount = segment[0].arg!!.integerValue()
val result = optimizedIntMultiplicationsOnStack(segment[1], amount)
result ?: " lda #${hexVal(segment[0])} | sta ${ESTACK_LO.toHex()},x | dex | jsr prog8_lib.mul_byte"
result ?: " lda #${hexVal(segment[0])} | sta $ESTACK_LO_HEX,x | dex | jsr prog8_lib.mul_byte"
},
AsmPattern(listOf(Opcode.PUSH_WORD, Opcode.MUL_W), listOf(Opcode.PUSH_WORD, Opcode.MUL_UW)) { segment ->
val amount = segment[0].arg!!.integerValue()
val result = optimizedIntMultiplicationsOnStack(segment[1], amount)
if (result != null) result else {
val value = hexVal(segment[0])
" lda #<$value | sta ${ESTACK_LO.toHex()},x | lda #>$value | sta ${ESTACK_HI.toHex()},x | dex | jsr prog8_lib.mul_word"
" lda #<$value | sta $ESTACK_LO_HEX,x | lda #>$value | sta $ESTACK_HI_HEX,x | dex | jsr prog8_lib.mul_word"
}
},
@@ -2289,6 +2310,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_PLUS1_HEX,x
cmp #>${segment[1].arg!!.integerValue().toHex()}
bne +
lda $ESTACK_LO_PLUS1_HEX,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_PLUS1_HEX,x | cmp #${segment[1].arg!!.integerValue().toHex()} """
}
)
@@ -1,242 +0,0 @@
package prog8.compiler.target.c64
import prog8.compiler.CompilationOptions
import prog8.compiler.CompilerException
import prog8.compiler.Zeropage
import prog8.compiler.ZeropageType
import java.awt.Color
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import kotlin.math.absoluteValue
import kotlin.math.pow
// 5-byte cbm MFLPT format limitations:
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
const val BASIC_LOAD_ADDRESS = 0x0801
const val RAW_LOAD_ADDRESS = 0xc000
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
const val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
const val ESTACK_HI = 0xcf00 // $cf00-$cfff inclusive
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
companion object {
const val SCRATCH_B1 = 0x02
const val SCRATCH_REG = 0x03 // temp storage for a register
const val SCRATCH_REG_X = 0xfa // temp storage for register X (the evaluation stack pointer)
const val SCRATCH_W1 = 0xfb // $fb+$fc
const val SCRATCH_W2 = 0xfd // $fd+$fe
}
override val exitProgramStrategy: ExitProgramStrategy = when(options.zeropage) {
ZeropageType.BASICSAFE -> ExitProgramStrategy.CLEAN_EXIT
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
}
init {
if(options.floats && options.zeropage!=ZeropageType.FLOATSAFE && options.zeropage!=ZeropageType.BASICSAFE)
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe'")
if(options.zeropage == ZeropageType.FULL) {
free.addAll(0x04 .. 0xf9)
free.add(0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1+1, SCRATCH_W2, SCRATCH_W2+1))
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
} else {
if(options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
0x22, 0x23, 0x24, 0x25,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
// 0x90-0xfa is 'kernel work storage area'
))
}
if(options.zeropage == ZeropageType.FLOATSAFE) {
// remove the zero page locations used for floating point operations from the free list
free.removeAll(listOf(
0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf
))
}
// add the other free Zp addresses,
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
free.addAll(listOf(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0d, 0x0e,
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
}
assert(SCRATCH_B1 !in free)
assert(SCRATCH_REG !in free)
assert(SCRATCH_REG_X !in free)
assert(SCRATCH_W1 !in free)
assert(SCRATCH_W2 !in free)
for(reserved in options.zpReserved)
reserve(reserved)
}
}
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) {
companion object {
const val MemorySize = 5
val zero = Mflpt5(0, 0,0,0,0)
fun fromNumber(num: Number): Mflpt5 {
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
// and https://en.wikipedia.org/wiki/IEEE_754-1985
val flt = num.toDouble()
if(flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
throw CompilerException("floating point number out of 5-byte mflpt range: $this")
if(flt==0.0)
return zero
val sign = if(flt<0.0) 0x80L else 0x00L
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
var mantissa = flt.absoluteValue
// if mantissa is too large, shift right and adjust exponent
while(mantissa >= 0x100000000) {
mantissa /= 2.0
exponent ++
}
// if mantissa is too small, shift left and adjust exponent
while(mantissa < 0x80000000) {
mantissa *= 2.0
exponent --
}
return when {
exponent<0 -> zero // underflow, use zero instead
exponent>255 -> throw CompilerException("floating point overflow: $this")
exponent==0 -> zero
else -> {
val mantLong = mantissa.toLong()
Mflpt5(
exponent.toShort(),
(mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(),
(mantLong.and(0x00ff0000L) ushr 16).toShort(),
(mantLong.and(0x0000ff00L) ushr 8).toShort(),
(mantLong.and(0x000000ffL)).toShort())
}
}
}
}
fun toDouble(): Double {
if(this == zero) return 0.0
val exp = b0 - 128
val sign = (b1.toInt() and 0x80) > 0
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
return if(sign) -result else result
}
}
object Charset {
private val normalImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-normal.png"))
private val shiftedImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-shifted.png"))
private fun scanChars(img: BufferedImage): Array<BufferedImage> {
val transparent = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB)
transparent.createGraphics().drawImage(img, 0, 0, null)
val black = Color(0,0,0).rgb
val nopixel = Color(0,0,0,0).rgb
for(y in 0 until transparent.height) {
for(x in 0 until transparent.width) {
val col = transparent.getRGB(x, y)
if(col==black)
transparent.setRGB(x, y, nopixel)
}
}
val numColumns = transparent.width / 8
val charImages = (0..255).map {
val charX = it % numColumns
val charY = it/ numColumns
transparent.getSubimage(charX*8, charY*8, 8, 8)
}
return charImages.toTypedArray()
}
val normalChars = scanChars(normalImg)
val shiftedChars = scanChars(shiftedImg)
private val coloredNormalChars = mutableMapOf<Short, Array<BufferedImage>>()
fun getColoredChar(screenCode: Short, color: Short): BufferedImage {
val colorIdx = (color % Colors.palette.size).toShort()
val chars = coloredNormalChars[colorIdx]
if(chars!=null)
return chars[screenCode.toInt()]
val coloredChars = mutableListOf<BufferedImage>()
val transparent = Color(0,0,0,0).rgb
val rgb = Colors.palette[colorIdx.toInt()].rgb
for(c in normalChars) {
val colored = c.copy()
for(y in 0 until colored.height)
for(x in 0 until colored.width) {
if(colored.getRGB(x, y)!=transparent) {
colored.setRGB(x, y, rgb)
}
}
coloredChars.add(colored)
}
coloredNormalChars[colorIdx] = coloredChars.toTypedArray()
return coloredNormalChars.getValue(colorIdx)[screenCode.toInt()]
}
}
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
}
object Colors {
val palette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
Color(0x000000), // 0 = black
Color(0xFFFFFF), // 1 = white
Color(0x813338), // 2 = red
Color(0x75cec8), // 3 = cyan
Color(0x8e3c97), // 4 = purple
Color(0x56ac4d), // 5 = green
Color(0x2e2c9b), // 6 = blue
Color(0xedf171), // 7 = yellow
Color(0x8e5029), // 8 = orange
Color(0x553800), // 9 = brown
Color(0xc46c71), // 10 = light red
Color(0x4a4a4a), // 11 = dark grey
Color(0x7b7b7b), // 12 = medium grey
Color(0xa9ff9f), // 13 = light green
Color(0x706deb), // 14 = light blue
Color(0xb2b2b2) // 15 = light grey
)
}
@@ -0,0 +1,247 @@
package prog8.compiler.target.c64
import prog8.compiler.*
import java.awt.Color
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import kotlin.math.absoluteValue
import kotlin.math.pow
object MachineDefinition {
// 5-byte cbm MFLPT format limitations:
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
const val BASIC_LOAD_ADDRESS = 0x0801
const val RAW_LOAD_ADDRESS = 0xc000
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
// and some heavily used string constants derived from the two values above
const val ESTACK_LO_VALUE = 0xce00 // $ce00-$ceff inclusive
const val ESTACK_HI_VALUE = 0xcf00 // $cf00-$cfff inclusive
const val ESTACK_LO_HEX = "\$ce00"
const val ESTACK_LO_PLUS1_HEX = "\$ce01"
const val ESTACK_LO_PLUS2_HEX = "\$ce02"
const val ESTACK_HI_HEX = "\$cf00"
const val ESTACK_HI_PLUS1_HEX = "\$cf01"
const val ESTACK_HI_PLUS2_HEX = "\$cf02"
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
companion object {
const val SCRATCH_B1 = 0x02
const val SCRATCH_REG = 0x03 // temp storage for a register
const val SCRATCH_REG_X = 0xfa // temp storage for register X (the evaluation stack pointer)
const val SCRATCH_W1 = 0xfb // $fb+$fc
const val SCRATCH_W2 = 0xfd // $fd+$fe
}
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
ZeropageType.BASICSAFE -> ExitProgramStrategy.CLEAN_EXIT
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
}
init {
if (options.floats && options.zeropage != ZeropageType.FLOATSAFE && options.zeropage != ZeropageType.BASICSAFE)
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe'")
if (options.zeropage == ZeropageType.FULL) {
free.addAll(0x04..0xf9)
free.add(0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
} else {
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
0x22, 0x23, 0x24, 0x25,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
// 0x90-0xfa is 'kernel work storage area'
))
}
if (options.zeropage == ZeropageType.FLOATSAFE) {
// remove the zero page locations used for floating point operations from the free list
free.removeAll(listOf(
0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf
))
}
// add the other free Zp addresses,
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
free.addAll(listOf(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0d, 0x0e,
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
}
assert(SCRATCH_B1 !in free)
assert(SCRATCH_REG !in free)
assert(SCRATCH_REG_X !in free)
assert(SCRATCH_W1 !in free)
assert(SCRATCH_W2 !in free)
for (reserved in options.zpReserved)
reserve(reserved)
}
}
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) {
companion object {
const val MemorySize = 5
val zero = Mflpt5(0, 0, 0, 0, 0)
fun fromNumber(num: Number): Mflpt5 {
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
// and https://en.wikipedia.org/wiki/IEEE_754-1985
val flt = num.toDouble()
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
throw CompilerException("floating point number out of 5-byte mflpt range: $this")
if (flt == 0.0)
return zero
val sign = if (flt < 0.0) 0x80L else 0x00L
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
var mantissa = flt.absoluteValue
// if mantissa is too large, shift right and adjust exponent
while (mantissa >= 0x100000000) {
mantissa /= 2.0
exponent++
}
// if mantissa is too small, shift left and adjust exponent
while (mantissa < 0x80000000) {
mantissa *= 2.0
exponent--
}
return when {
exponent < 0 -> zero // underflow, use zero instead
exponent > 255 -> throw CompilerException("floating point overflow: $this")
exponent == 0 -> zero
else -> {
val mantLong = mantissa.toLong()
Mflpt5(
exponent.toShort(),
(mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(),
(mantLong.and(0x00ff0000L) ushr 16).toShort(),
(mantLong.and(0x0000ff00L) ushr 8).toShort(),
(mantLong.and(0x000000ffL)).toShort())
}
}
}
}
fun toDouble(): Double {
if (this == zero) return 0.0
val exp = b0 - 128
val sign = (b1.toInt() and 0x80) > 0
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
return if (sign) -result else result
}
}
object Charset {
private val normalImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-normal.png"))
private val shiftedImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-shifted.png"))
private fun scanChars(img: BufferedImage): Array<BufferedImage> {
val transparent = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB)
transparent.createGraphics().drawImage(img, 0, 0, null)
val black = Color(0, 0, 0).rgb
val nopixel = Color(0, 0, 0, 0).rgb
for (y in 0 until transparent.height) {
for (x in 0 until transparent.width) {
val col = transparent.getRGB(x, y)
if (col == black)
transparent.setRGB(x, y, nopixel)
}
}
val numColumns = transparent.width / 8
val charImages = (0..255).map {
val charX = it % numColumns
val charY = it / numColumns
transparent.getSubimage(charX * 8, charY * 8, 8, 8)
}
return charImages.toTypedArray()
}
val normalChars = scanChars(normalImg)
val shiftedChars = scanChars(shiftedImg)
private val coloredNormalChars = mutableMapOf<Short, Array<BufferedImage>>()
fun getColoredChar(screenCode: Short, color: Short): BufferedImage {
val colorIdx = (color % colorPalette.size).toShort()
val chars = coloredNormalChars[colorIdx]
if (chars != null)
return chars[screenCode.toInt()]
val coloredChars = mutableListOf<BufferedImage>()
val transparent = Color(0, 0, 0, 0).rgb
val rgb = colorPalette[colorIdx.toInt()].rgb
for (c in normalChars) {
val colored = c.copy()
for (y in 0 until colored.height)
for (x in 0 until colored.width) {
if (colored.getRGB(x, y) != transparent) {
colored.setRGB(x, y, rgb)
}
}
coloredChars.add(colored)
}
coloredNormalChars[colorIdx] = coloredChars.toTypedArray()
return coloredNormalChars.getValue(colorIdx)[screenCode.toInt()]
}
}
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
}
val colorPalette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
Color(0x000000), // 0 = black
Color(0xFFFFFF), // 1 = white
Color(0x813338), // 2 = red
Color(0x75cec8), // 3 = cyan
Color(0x8e3c97), // 4 = purple
Color(0x56ac4d), // 5 = green
Color(0x2e2c9b), // 6 = blue
Color(0xedf171), // 7 = yellow
Color(0x8e5029), // 8 = orange
Color(0x553800), // 9 = brown
Color(0xc46c71), // 10 = light red
Color(0x4a4a4a), // 11 = dark grey
Color(0x7b7b7b), // 12 = medium grey
Color(0xa9ff9f), // 13 = light green
Color(0x706deb), // 14 = light blue
Color(0xb2b2b2) // 15 = light grey
)
}
@@ -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()
}
}
}
@@ -8,6 +8,16 @@ import prog8.compiler.intermediate.Opcode
import prog8.compiler.toHex
import prog8.vm.stackvm.Syscall
import prog8.vm.stackvm.syscallsForStackVm
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS2_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS2_HEX
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
private var breakpointCounter = 0
@@ -61,13 +71,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_PLUS1_HEX,x | sta $ESTACK_LO_HEX,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_PLUS1_HEX,x | sta $ESTACK_LO_HEX,x | lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_HI_HEX,x | dex "
}
Opcode.DISCARD_FLOAT -> " inx | inx | inx"
Opcode.CMP_B, Opcode.CMP_UB -> {
" inx | lda $ESTACK_LO_HEX,x | cmp #${ins.arg!!.integerValue().toHex()} | ;CMP_B "
}
Opcode.CMP_W, Opcode.CMP_UW -> {
"""
inx
lda $ESTACK_HI_HEX,x
cmp #>${ins.arg!!.integerValue().toHex()}
bne +
lda $ESTACK_LO_HEX,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()}"
@@ -108,11 +137,11 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
}
Opcode.PUSH_BYTE -> {
" lda #${hexVal(ins)} | sta ${ESTACK_LO.toHex()},x | dex"
" lda #${hexVal(ins)} | sta $ESTACK_LO_HEX,x | dex"
}
Opcode.PUSH_WORD -> {
val value = hexVal(ins)
" lda #<$value | sta ${ESTACK_LO.toHex()},x | lda #>$value | sta ${ESTACK_HI.toHex()},x | dex"
" lda #<$value | sta $ESTACK_LO_HEX,x | lda #>$value | sta $ESTACK_HI_HEX,x | dex"
}
Opcode.PUSH_FLOAT -> {
val floatConst = getFloatConst(ins.arg!!)
@@ -121,28 +150,28 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.PUSH_VAR_BYTE -> {
when(ins.callLabel) {
"X" -> throw CompilerException("makes no sense to push X, it's used as a stack pointer itself. You should probably not use the X register (or only in trivial assignments)")
"A" -> " sta ${ESTACK_LO.toHex()},x | dex"
"Y" -> " tya | sta ${ESTACK_LO.toHex()},x | dex"
else -> " lda ${ins.callLabel} | sta ${ESTACK_LO.toHex()},x | dex"
"A" -> " sta $ESTACK_LO_HEX,x | dex"
"Y" -> " tya | sta $ESTACK_LO_HEX,x | dex"
else -> " lda ${ins.callLabel} | sta $ESTACK_LO_HEX,x | dex"
}
}
Opcode.PUSH_VAR_WORD -> {
" lda ${ins.callLabel} | sta ${ESTACK_LO.toHex()},x | lda ${ins.callLabel}+1 | sta ${ESTACK_HI.toHex()},x | dex"
" lda ${ins.callLabel} | sta $ESTACK_LO_HEX,x | lda ${ins.callLabel}+1 | sta $ESTACK_HI_HEX,x | dex"
}
Opcode.PUSH_VAR_FLOAT -> " lda #<${ins.callLabel} | ldy #>${ins.callLabel}| jsr c64flt.push_float"
Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB -> {
"""
lda ${hexVal(ins)}
sta ${ESTACK_LO.toHex()},x
sta $ESTACK_LO_HEX,x
dex
"""
}
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW -> {
"""
lda ${hexVal(ins)}
sta ${ESTACK_LO.toHex()},x
sta $ESTACK_LO_HEX,x
lda ${hexValPlusOne(ins)}
sta ${ESTACK_HI.toHex()},x
sta $ESTACK_HI_HEX,x
dex
"""
}
@@ -151,43 +180,43 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
}
Opcode.PUSH_MEMREAD -> {
"""
lda ${(ESTACK_LO+1).toHex()},x
lda $ESTACK_LO_PLUS1_HEX,x
sta (+) +1
lda ${(ESTACK_HI+1).toHex()},x
lda $ESTACK_HI_PLUS1_HEX,x
sta (+) +2
+ lda 65535 ; modified
sta ${(ESTACK_LO+1).toHex()},x
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.PUSH_REGAY_WORD -> {
" sta ${ESTACK_LO.toHex()},x | tya | sta ${ESTACK_HI.toHex()},x | dex "
" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex "
}
Opcode.PUSH_ADDR_HEAPVAR -> {
" lda #<${ins.callLabel} | sta ${ESTACK_LO.toHex()},x | lda #>${ins.callLabel} | sta ${ESTACK_HI.toHex()},x | dex"
" lda #<${ins.callLabel} | sta $ESTACK_LO_HEX,x | lda #>${ins.callLabel} | sta $ESTACK_HI_HEX,x | dex"
}
Opcode.POP_REGAX_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
Opcode.POP_REGXY_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
Opcode.POP_REGAY_WORD -> {
" inx | lda ${ESTACK_LO.toHex()},x | ldy ${ESTACK_HI.toHex()},x "
" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x "
}
Opcode.READ_INDEXED_VAR_BYTE -> {
"""
ldy ${(ESTACK_LO+1).toHex()},x
ldy $ESTACK_LO_PLUS1_HEX,x
lda ${ins.callLabel},y
sta ${(ESTACK_LO+1).toHex()},x
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.READ_INDEXED_VAR_WORD -> {
"""
lda ${(ESTACK_LO+1).toHex()},x
lda $ESTACK_LO_PLUS1_HEX,x
asl a
tay
lda ${ins.callLabel},y
sta ${(ESTACK_LO+1).toHex()},x
sta $ESTACK_LO_PLUS1_HEX,x
lda ${ins.callLabel}+1,y
sta ${(ESTACK_HI+1).toHex()},x
sta $ESTACK_HI_PLUS1_HEX,x
"""
}
Opcode.READ_INDEXED_VAR_FLOAT -> {
@@ -200,22 +229,22 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.WRITE_INDEXED_VAR_BYTE -> {
"""
inx
ldy ${ESTACK_LO.toHex()},x
ldy $ESTACK_LO_HEX,x
inx
lda ${ESTACK_LO.toHex()},x
lda $ESTACK_LO_HEX,x
sta ${ins.callLabel},y
"""
}
Opcode.WRITE_INDEXED_VAR_WORD -> {
"""
inx
lda ${ESTACK_LO.toHex()},x
lda $ESTACK_LO_HEX,x
asl a
tay
inx
lda ${ESTACK_LO.toHex()},x
lda $ESTACK_LO_HEX,x
sta ${ins.callLabel},y
lda ${ESTACK_HI.toHex()},x
lda $ESTACK_HI_HEX,x
sta ${ins.callLabel}+1,y
"""
}
@@ -229,16 +258,16 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.POP_MEM_BYTE -> {
"""
inx
lda ${ESTACK_LO.toHex()},x
lda $ESTACK_LO_HEX,x
sta ${hexVal(ins)}
"""
}
Opcode.POP_MEM_WORD -> {
"""
inx
lda ${ESTACK_LO.toHex()},x
lda $ESTACK_LO_HEX,x
sta ${hexVal(ins)}
lda ${ESTACK_HI.toHex()},x
lda $ESTACK_HI_HEX,x
sta ${hexValPlusOne(ins)}
"""
}
@@ -248,12 +277,12 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.POP_MEMWRITE -> {
"""
inx
lda ${ESTACK_LO.toHex()},x
lda $ESTACK_LO_HEX,x
sta (+) +1
lda ${ESTACK_HI.toHex()},x
lda $ESTACK_HI_HEX,x
sta (+) +2
inx
lda ${ESTACK_LO.toHex()},x
lda $ESTACK_LO_HEX,x
+ sta 65535 ; modified
"""
}
@@ -261,13 +290,13 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.POP_VAR_BYTE -> {
when (ins.callLabel) {
"X" -> throw CompilerException("makes no sense to pop X, it's used as a stack pointer itself")
"A" -> " inx | lda ${ESTACK_LO.toHex()},x"
"Y" -> " inx | ldy ${ESTACK_LO.toHex()},x"
else -> " inx | lda ${ESTACK_LO.toHex()},x | sta ${ins.callLabel}"
"A" -> " inx | lda $ESTACK_LO_HEX,x"
"Y" -> " inx | ldy $ESTACK_LO_HEX,x"
else -> " inx | lda $ESTACK_LO_HEX,x | sta ${ins.callLabel}"
}
}
Opcode.POP_VAR_WORD -> {
" inx | lda ${ESTACK_LO.toHex()},x | ldy ${ESTACK_HI.toHex()},x | sta ${ins.callLabel} | sty ${ins.callLabel}+1"
" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x | sta ${ins.callLabel} | sty ${ins.callLabel}+1"
}
Opcode.POP_VAR_FLOAT -> {
" lda #<${ins.callLabel} | ldy #>${ins.callLabel} | jsr c64flt.pop_float"
@@ -294,9 +323,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.POP_INC_MEMORY -> {
"""
inx
lda ${ESTACK_LO.toHex()},x
lda $ESTACK_LO_HEX,x
sta (+) +1
lda ${ESTACK_HI.toHex()},x
lda $ESTACK_HI_HEX,x
sta (+) +2
+ inc 65535 ; modified
"""
@@ -304,9 +333,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.POP_DEC_MEMORY -> {
"""
inx
lda ${ESTACK_LO.toHex()},x
lda $ESTACK_LO_HEX,x
sta (+) +1
lda ${ESTACK_HI.toHex()},x
lda $ESTACK_HI_HEX,x
sta (+) +2
+ dec 65535 ; modified
"""
@@ -331,8 +360,8 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
}
Opcode.INC_MEMORY -> " inc ${hexVal(ins)}"
Opcode.DEC_MEMORY -> " dec ${hexVal(ins)}"
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> " inx | txa | pha | lda ${ESTACK_LO.toHex()},x | tax | inc ${ins.callLabel},x | pla | tax"
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> " inx | txa | pha | lda ${ESTACK_LO.toHex()},x | tax | dec ${ins.callLabel},x | pla | tax"
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> " inx | txa | pha | lda $ESTACK_LO_HEX,x | tax | inc ${ins.callLabel},x | pla | tax"
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> " inx | txa | pha | lda $ESTACK_LO_HEX,x | tax | dec ${ins.callLabel},x | pla | tax"
Opcode.NEG_B -> " jsr prog8_lib.neg_b"
Opcode.NEG_W -> " jsr prog8_lib.neg_w"
@@ -343,9 +372,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.POW_F -> " jsr c64flt.pow_f"
Opcode.INV_BYTE -> {
"""
lda ${(ESTACK_LO + 1).toHex()},x
lda $ESTACK_LO_PLUS1_HEX,x
eor #255
sta ${(ESTACK_LO + 1).toHex()},x
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.INV_WORD -> " jsr prog8_lib.inv_word"
@@ -387,7 +416,7 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
val label = ins.callLabel ?: hexVal(ins)
"""
inx
lda ${(ESTACK_LO).toHex()},x
lda $ESTACK_LO_HEX,x
beq $label
"""
}
@@ -395,9 +424,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
val label = ins.callLabel ?: hexVal(ins)
"""
inx
lda ${(ESTACK_LO).toHex()},x
lda $ESTACK_LO_HEX,x
beq $label
lda ${(ESTACK_HI).toHex()},x
lda $ESTACK_HI_HEX,x
beq $label
"""
}
@@ -405,7 +434,7 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
val label = ins.callLabel ?: hexVal(ins)
"""
inx
lda ${(ESTACK_LO).toHex()},x
lda $ESTACK_LO_HEX,x
bne $label
"""
}
@@ -413,9 +442,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
val label = ins.callLabel ?: hexVal(ins)
"""
inx
lda ${(ESTACK_LO).toHex()},x
lda $ESTACK_LO_HEX,x
bne $label
lda ${(ESTACK_HI).toHex()},x
lda $ESTACK_HI_HEX,x
bne $label
"""
}
@@ -435,27 +464,27 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.CAST_F_TO_B -> " jsr c64flt.stack_float2w"
Opcode.CAST_F_TO_UW -> " jsr c64flt.stack_float2uw"
Opcode.CAST_F_TO_W -> " jsr c64flt.stack_float2w"
Opcode.CAST_UB_TO_UW, Opcode.CAST_UB_TO_W -> " lda #0 | sta ${(ESTACK_HI+1).toHex()},x" // clear the msb
Opcode.CAST_B_TO_UW, Opcode.CAST_B_TO_W -> " lda ${(ESTACK_LO+1).toHex()},x | ${signExtendA("${(ESTACK_HI+1).toHex()},x")}" // sign extend the lsb
Opcode.MSB -> " lda ${(ESTACK_HI+1).toHex()},x | sta ${(ESTACK_LO+1).toHex()},x"
Opcode.MKWORD -> " inx | lda ${ESTACK_LO.toHex()},x | sta ${(ESTACK_HI+1).toHex()},x "
Opcode.CAST_UB_TO_UW, Opcode.CAST_UB_TO_W -> " lda #0 | sta $ESTACK_HI_PLUS1_HEX,x" // clear the msb
Opcode.CAST_B_TO_UW, Opcode.CAST_B_TO_W -> " lda $ESTACK_LO_PLUS1_HEX,x | ${signExtendA("$ESTACK_HI_PLUS1_HEX,x")}" // sign extend the lsb
Opcode.MSB -> " lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x"
Opcode.MKWORD -> " inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x "
Opcode.ADD_UB, Opcode.ADD_B -> { // TODO inline better (pattern with more opcodes)
"""
lda ${(ESTACK_LO + 2).toHex()},x
lda $ESTACK_LO_PLUS2_HEX,x
clc
adc ${(ESTACK_LO + 1).toHex()},x
adc $ESTACK_LO_PLUS1_HEX,x
inx
sta ${(ESTACK_LO + 1).toHex()},x
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.SUB_UB, Opcode.SUB_B -> { // TODO inline better (pattern with more opcodes)
"""
lda ${(ESTACK_LO + 2).toHex()},x
lda $ESTACK_LO_PLUS2_HEX,x
sec
sbc ${(ESTACK_LO + 1).toHex()},x
sbc $ESTACK_LO_PLUS1_HEX,x
inx
sta ${(ESTACK_LO + 1).toHex()},x
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.ADD_W, Opcode.ADD_UW -> " jsr prog8_lib.add_w"
@@ -519,12 +548,12 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.LESSEQ_W -> " jsr prog8_lib.lesseq_w"
Opcode.LESSEQ_F -> " jsr c64flt.lesseq_f"
Opcode.SHIFTEDL_BYTE -> " asl ${(ESTACK_LO+1).toHex()},x"
Opcode.SHIFTEDL_WORD -> " asl ${(ESTACK_LO+1).toHex()},x | rol ${(ESTACK_HI+1).toHex()},x"
Opcode.SHIFTEDR_SBYTE -> " lda ${(ESTACK_LO+1).toHex()},x | asl a | ror ${(ESTACK_LO+1).toHex()},x"
Opcode.SHIFTEDR_UBYTE -> " lsr ${(ESTACK_LO+1).toHex()},x"
Opcode.SHIFTEDR_SWORD -> " lda ${(ESTACK_HI+1).toHex()},x | asl a | ror ${(ESTACK_HI+1).toHex()},x | ror ${(ESTACK_LO+1).toHex()},x"
Opcode.SHIFTEDR_UWORD -> " lsr ${(ESTACK_HI+1).toHex()},x | ror ${(ESTACK_LO+1).toHex()},x"
Opcode.SHIFTEDL_BYTE -> " asl $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDL_WORD -> " asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x"
Opcode.SHIFTEDR_SBYTE -> " lda $ESTACK_LO_PLUS1_HEX,x | asl a | ror $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDR_UBYTE -> " lsr $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDR_SWORD -> " lda $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDR_UWORD -> " lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x"
else -> null
}
@@ -12,10 +12,14 @@ import kotlin.math.*
class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType>)
typealias ConstExpressionCaller = (args: List<IExpression>, position: Position, program: Program) -> LiteralValue
class FunctionSignature(val pure: Boolean, // does it have side effects?
val parameters: List<BuiltinFunctionParam>,
val returntype: DataType?,
val constExpressionFunc: ((args: List<IExpression>, position: Position, program: Program) -> LiteralValue)? = null)
val constExpressionFunc: ConstExpressionCaller? = null)
val BuiltinFunctions = mapOf(
@@ -127,8 +131,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 +148,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 +156,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 +280,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)
+23 -6
View File
@@ -1,6 +1,7 @@
package prog8.optimizer
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.ParentSentinel
import prog8.ast.base.VarDeclType
import prog8.ast.base.initvarsSubName
@@ -104,7 +105,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)
@@ -113,10 +119,14 @@ class CallGraph(private val program: Program): IAstVisitor {
}
override fun visit(decl: VarDecl) {
if(decl.autoGenerated || (decl.definingModule().isLibraryModule && decl.type!=VarDeclType.VAR)) {
if(decl.hiddenButDoNotRemove || (decl.definingModule().isLibraryModule && decl.type!=VarDeclType.VAR)) {
// make sure autogenerated vardecls are in the used symbols
addNodeAndParentScopes(decl)
}
if(decl.datatype==DataType.STRUCT)
addNodeAndParentScopes(decl)
super.visit(decl)
}
@@ -153,6 +163,11 @@ class CallGraph(private val program: Program): IAstVisitor {
super.visit(jump)
}
override fun visit(structDecl: StructDecl) {
usedSymbols.add(structDecl)
usedSymbols.addAll(structDecl.statements)
}
override fun visit(inlineAssembly: InlineAssembly) {
// parse inline asm for subroutine calls (jmp, jsr)
val scope = inlineAssembly.definingScope()
@@ -186,10 +201,12 @@ class CallGraph(private val program: Program): IAstVisitor {
if (matches2 != null) {
val target= matches2.groups[2]?.value
if (target != null && (target[0].isLetter() || target[0] == '_')) {
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
if (node is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
if(target.contains('.')) {
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
if (node is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
}
}
}
}
@@ -7,8 +7,8 @@ import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.statements.*
import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
import kotlin.math.floor
@@ -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
}
@@ -175,6 +175,9 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval.position)
}
}
DataType.STRUCT -> {
// leave it alone for structs.
}
else -> throw FatalAstException("invalid array vardecl type ${decl.datatype}")
}
}
@@ -656,7 +659,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)
@@ -31,14 +31,6 @@ internal fun Program.constantFold() {
internal fun Program.optimizeStatements(optimizeInlining: Boolean): Int {
val optimizer = StatementOptimizer(this, optimizeInlining)
optimizer.visit(this)
for(scope in optimizer.scopesToFlatten.reversed()) {
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.addAll(idx, scope.statements)
}
}
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
return optimizer.optimizationsDone
@@ -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
+161 -141
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,10 @@ 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 +34,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 +78,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 +141,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 +161,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 +185,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
@@ -198,10 +194,10 @@ internal class StatementOptimizer(private val program: Program, private val opti
override fun visit(decl: VarDecl): IStatement {
val forceOutput = "force_output" in decl.definingBlock().options()
if(decl !in callgraph.usedSymbols && !forceOutput) {
if(decl.type!=VarDeclType.CONST)
printWarning("removing unused variable '${decl.name}'", decl.position)
if(decl.type == VarDeclType.VAR)
printWarning("removing unused variable ${decl.type} '${decl.name}'", decl.position)
optimizationsDone++
return NopStatement(decl.position) // remove unused variable
return NopStatement.insteadOf(decl)
}
return super.visit(decl)
@@ -219,9 +215,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 +234,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 +279,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 +297,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 +311,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 +345,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 +361,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 +390,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 +426,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 +477,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 +488,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 +611,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
}
}
return super.visit(assignment)
}
@@ -631,11 +620,6 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(linesToRemove.isNotEmpty()) {
linesToRemove.reversed().forEach{scope.statements.removeAt(it)}
}
if(scope.parent is INameScope) {
scopesToFlatten.add(scope) // get rid of the anonymous scope
}
return super.visit(scope)
}
@@ -644,7 +628,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 +636,39 @@ internal class StatementOptimizer(private val program: Program, private val opti
internal class FlattenAnonymousScopesAndRemoveNops: IAstVisitor {
private var scopesToFlatten = mutableListOf<INameScope>()
private val nopStatements = mutableListOf<NopStatement>()
override fun visit(program: Program) {
super.visit(program)
for(scope in scopesToFlatten.reversed()) {
val namescope = scope.parent as INameScope
val idx = namescope.statements.indexOf(scope as IStatement)
if(idx>=0) {
val nop = NopStatement.insteadOf(namescope.statements[idx])
nop.parent = namescope as Node
namescope.statements[idx] = nop
namescope.statements.addAll(idx, scope.statements)
scope.statements.forEach { it.parent = namescope }
visit(nop)
}
}
this.nopStatements.forEach {
it.definingScope().remove(it)
}
}
override fun visit(scope: AnonymousScope) {
if(scope.parent is INameScope) {
scopesToFlatten.add(scope) // get rid of the anonymous scope
}
return super.visit(scope)
}
override fun visit(nopStatement: NopStatement) {
nopStatements.add(nopStatement)
}
}
+155 -39
View File
@@ -2,6 +2,8 @@ package prog8.vm
import prog8.ast.base.*
import prog8.ast.expressions.LiteralValue
import prog8.ast.statements.StructDecl
import prog8.ast.statements.ZeropageWish
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.Petscii
import kotlin.math.abs
@@ -13,7 +15,8 @@ import kotlin.math.pow
* this runtime value can be used to *execute* the parsed Ast (or another intermediary form)
* It contains a value of a variable during run time of the program and provides arithmetic operations on the value.
*/
open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=null, val array: Array<Number>?=null, val heapId: Int?=null) {
open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=null,
val array: Array<Number>?=null, val heapId: Int?=null) {
val byteval: Short?
val wordval: Int?
@@ -26,7 +29,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 +43,18 @@ 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 {
TODO("ADDRESSOF ${elt.value}")
}
}
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 +63,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 +124,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 +199,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 +312,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 +465,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 +498,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 +532,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 +554,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 +563,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())
+409 -88
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.MachineDefinition
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>, startAtLabel: 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)
@@ -259,10 +282,10 @@ class AstVm(val program: Program) {
}
val statements = sub.statements.iterator()
if(startlabel!=null) {
if(startAtLabel!=null) {
do {
val stmt = statements.next()
} while(stmt!==startlabel)
} while(stmt!==startAtLabel)
}
try {
@@ -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* MachineDefinition.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")
}
}
}
@@ -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")
}
}
}
+36 -22
View File
@@ -3,6 +3,7 @@ package prog8.vm.astvm
import prog8.ast.*
import prog8.ast.base.ArrayElementTypes
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
@@ -13,9 +14,15 @@ import prog8.vm.RuntimeValue
import prog8.vm.RuntimeValueRange
import kotlin.math.abs
typealias BuiltinfunctionCaller = (name: String, args: List<RuntimeValue>, flags: StatusFlags) -> RuntimeValue?
typealias SubroutineCaller = (sub: Subroutine, args: List<RuntimeValue>, startAtLabel: Label?) -> RuntimeValue?
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: BuiltinfunctionCaller,
val executeSubroutine: SubroutineCaller)
fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
val constval = expr.constValue(ctx.program)
@@ -81,8 +88,14 @@ fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
}
is AddressOf -> {
// we support: address of heap var -> the heap id
val heapId = expr.identifier.heapId(ctx.program.namespace)
return RuntimeValue(DataType.UWORD, heapId)
return try {
val heapId = expr.identifier.heapId(ctx.program.namespace)
RuntimeValue(DataType.UWORD, heapId)
} catch( f: FatalAstException) {
// fallback: use the hash of the name, so we have at least *a* value...
val address = expr.identifier.hashCode() and 65535
RuntimeValue(DataType.UWORD, address)
}
}
is DirectMemoryRead -> {
val address = evaluate(expr.addressExpression, ctx).wordval!!
@@ -93,19 +106,21 @@ fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
val scope = expr.definingScope()
val variable = scope.lookup(expr.nameInSource, expr)
if(variable is VarDecl) {
if(variable.type==VarDeclType.VAR)
return ctx.runtimeVars.get(variable.definingScope(), variable.name)
else {
val address = ctx.runtimeVars.getMemoryAddress(variable.definingScope(), variable.name)
return when(variable.datatype) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address))
DataType.BYTE -> RuntimeValue(DataType.BYTE, ctx.mem.getSByte(address))
DataType.UWORD -> RuntimeValue(DataType.UWORD, ctx.mem.getUWord(address))
DataType.WORD -> RuntimeValue(DataType.WORD, ctx.mem.getSWord(address))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, ctx.mem.getFloat(address))
DataType.STR -> RuntimeValue(DataType.STR, str = ctx.mem.getString(address))
DataType.STR_S -> RuntimeValue(DataType.STR_S, str = ctx.mem.getScreencodeString(address))
else -> throw VmExecutionException("unexpected datatype $variable")
when {
variable.type==VarDeclType.VAR -> return ctx.runtimeVars.get(variable.definingScope(), variable.name)
variable.datatype==DataType.STRUCT -> throw VmExecutionException("cannot process structs by-value. at ${expr.position}")
else -> {
val address = ctx.runtimeVars.getMemoryAddress(variable.definingScope(), variable.name)
return when(variable.datatype) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address))
DataType.BYTE -> RuntimeValue(DataType.BYTE, ctx.mem.getSByte(address))
DataType.UWORD -> RuntimeValue(DataType.UWORD, ctx.mem.getUWord(address))
DataType.WORD -> RuntimeValue(DataType.WORD, ctx.mem.getSWord(address))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, ctx.mem.getFloat(address))
DataType.STR -> RuntimeValue(DataType.STR, str = ctx.mem.getString(address))
DataType.STR_S -> RuntimeValue(DataType.STR_S, str = ctx.mem.getScreencodeString(address))
else -> throw VmExecutionException("unexpected datatype $variable")
}
}
}
} else
@@ -116,13 +131,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
}
+3 -3
View File
@@ -1,6 +1,6 @@
package prog8.vm.astvm
import prog8.compiler.target.c64.Mflpt5
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.Petscii
import kotlin.math.abs
@@ -80,7 +80,7 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
}
fun setFloat(address: Int, value: Double) {
val mflpt5 = Mflpt5.fromNumber(value)
val mflpt5 = MachineDefinition.Mflpt5.fromNumber(value)
setUByte(address, mflpt5.b0)
setUByte(address+1, mflpt5.b1)
setUByte(address+2, mflpt5.b2)
@@ -89,7 +89,7 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
}
fun getFloat(address: Int): Double {
return Mflpt5(getUByte(address), getUByte(address + 1), getUByte(address + 2),
return MachineDefinition.Mflpt5(getUByte(address), getUByte(address + 1), getUByte(address + 2),
getUByte(address + 3), getUByte(address + 4)).toDouble()
}
+68 -50
View File
@@ -1,12 +1,12 @@
package prog8.vm.astvm
import prog8.compiler.target.c64.Charset
import prog8.compiler.target.c64.Colors
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.Petscii
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 +18,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 +31,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?) {
@@ -49,61 +50,82 @@ class BitmapScreenPanel : KeyListener, JPanel() {
}
fun clearScreen(color: Short) {
g2d.background = Colors.palette[color % Colors.palette.size]
g2d.background = MachineDefinition.colorPalette[color % MachineDefinition.colorPalette.size]
g2d.clearRect(0, 0, SCREENWIDTH, SCREENHEIGHT)
cursorX = 0
cursorY = 0
}
fun setPixel(x: Int, y: Int, color: Short) {
image.setRGB(x, y, Colors.palette[color % Colors.palette.size].rgb)
image.setRGB(x, y, MachineDefinition.colorPalette[color % MachineDefinition.colorPalette.size].rgb)
}
fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int, color: Short) {
g2d.color = Colors.palette[color % Colors.palette.size]
g2d.color = MachineDefinition.colorPalette[color % MachineDefinition.colorPalette.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 = MachineDefinition.colorPalette[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 % MachineDefinition.colorPalette.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 colorIdx = (color % MachineDefinition.colorPalette.size).toShort()
val screencode = Petscii.petscii2scr(petscii, inverseVideo)
val coloredImage = MachineDefinition.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 % MachineDefinition.colorPalette.size).toShort()
val coloredImage = MachineDefinition.Charset.getColoredChar(screencode, colorIdx)
g2d.drawImage(coloredImage, 8*x, 8*y , null)
}
@@ -116,20 +138,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,29 +148,30 @@ 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)
val borderTop = JPanel().apply {
preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = Colors.palette[14]
background = MachineDefinition.colorPalette[14]
}
val borderBottom = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = Colors.palette[14]
background = MachineDefinition.colorPalette[14]
}
val borderLeft = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = Colors.palette[14]
background = MachineDefinition.colorPalette[14]
}
val borderRight = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = Colors.palette[14]
background = MachineDefinition.colorPalette[14]
}
var c = GridBagConstraints()
c.gridx=0; c.gridy=1; c.gridwidth=3
@@ -189,3 +198,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
}
+24 -14
View File
@@ -4,7 +4,9 @@ import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.LiteralValue
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl
import prog8.ast.statements.ZeropageWish
import prog8.compiler.HeapValues
import prog8.vm.RuntimeValue
@@ -17,9 +19,12 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v
runtimeVariables.define(program.namespace, Register.Y.name, RuntimeValue(DataType.UBYTE, 0))
val globalpos = Position("<<global>>", 0, 0, 0)
val vdA = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.A.name, LiteralValue.optimalInteger(0, globalpos), isArray = false, autoGenerated = true, position = globalpos)
val vdX = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.X.name, LiteralValue.optimalInteger(255, globalpos), isArray = false, autoGenerated = true, position = globalpos)
val vdY = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.Y.name, LiteralValue.optimalInteger(0, globalpos), isArray = false, autoGenerated = true, position = globalpos)
val vdA = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.A.name, null,
LiteralValue.optimalInteger(0, globalpos), isArray = false, hiddenButDoNotRemove = true, position = globalpos)
val vdX = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.X.name, null,
LiteralValue.optimalInteger(255, globalpos), isArray = false, hiddenButDoNotRemove = true, position = globalpos)
val vdY = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.Y.name, null,
LiteralValue.optimalInteger(0, globalpos), isArray = false, hiddenButDoNotRemove = true, position = globalpos)
vdA.linkParents(program.namespace)
vdX.linkParents(program.namespace)
vdY.linkParents(program.namespace)
@@ -31,17 +36,22 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v
}
override fun visit(decl: VarDecl): IStatement {
when(decl.type) {
// we can assume the value in the vardecl already has been converted into a constant LiteralValue here.
VarDeclType.VAR -> {
val value = RuntimeValue.from(decl.value as LiteralValue, heap)
runtimeVariables.define(decl.definingScope(), decl.name, value)
}
VarDeclType.MEMORY -> {
runtimeVariables.defineMemory(decl.definingScope(), decl.name, (decl.value as LiteralValue).asIntegerValue!!)
}
VarDeclType.CONST -> {
// consts should have been const-folded away
// if the decl is part of a struct, just skip it
if(decl.parent !is StructDecl) {
when (decl.type) {
// we can assume the value in the vardecl already has been converted into a constant LiteralValue here.
VarDeclType.VAR -> {
if(decl.datatype!=DataType.STRUCT) {
val value = RuntimeValue.from(decl.value as LiteralValue, heap)
runtimeVariables.define(decl.definingScope(), decl.name, value)
}
}
VarDeclType.MEMORY -> {
runtimeVariables.defineMemory(decl.definingScope(), decl.name, (decl.value as LiteralValue).asIntegerValue!!)
}
VarDeclType.CONST -> {
// consts should have been const-folded away
}
}
}
return super.visit(decl)
+38 -33
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
}
@@ -1875,10 +1873,17 @@ class StackVm(private var traceOutputFile: String?) {
Opcode.INLINE_ASSEMBLY -> throw VmExecutionException("stackVm doesn't support executing inline assembly code $ins")
Opcode.INCLUDE_FILE -> throw VmExecutionException("stackVm doesn't support including a file $ins")
Opcode.PUSH_ADDR_HEAPVAR -> {
val heapId = variables.getValue(ins.callLabel!!).heapId!!
if(heapId<0)
throw VmExecutionException("expected variable on heap")
evalstack.push(RuntimeValue(DataType.UWORD, heapId)) // push the "address" of the string or array variable (this is taken care of properly in the assembly code generator)
val variable = variables.getValue(ins.callLabel!!)
if(variable.heapId!=null) {
val heapId = variable.heapId
if (heapId < 0)
throw VmExecutionException("expected variable on heap")
evalstack.push(RuntimeValue(DataType.UWORD, heapId)) // push the "address" of the string or array variable (this is taken care of properly in the assembly code generator)
} else {
// hack: return hash of the name, so we have at least *a* value...
val addr = ins.callLabel.hashCode() and 65535
evalstack.push(RuntimeValue(DataType.UWORD, addr))
}
}
Opcode.CAST_UB_TO_B -> typecast(DataType.UBYTE, DataType.BYTE)
Opcode.CAST_W_TO_B -> typecast(DataType.WORD, DataType.BYTE)
@@ -1928,7 +1933,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 +2034,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 +2316,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 +2417,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()
+169 -36
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())
}
}
+2 -2
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))
}
}
+5 -1
View File
@@ -10,7 +10,11 @@ import prog8.ast.base.Position
import prog8.ast.expressions.LiteralValue
import prog8.vm.RuntimeValue
import prog8.compiler.*
import prog8.compiler.target.c64.*
import prog8.compiler.target.c64.MachineDefinition.Mflpt5
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.Petscii
import java.io.CharConversionException
import kotlin.test.*
+3 -1
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>
+54 -17
View File
@@ -2,6 +2,8 @@
Writing and building a program
==============================
.. _building_compiler:
First, getting a working compiler
---------------------------------
@@ -11,20 +13,38 @@ Then you can choose a few ways to get a compiler:
**Download a precompiled version from github:**
#. download a recent "prog8compiler.jar" from `the releases on Github <https://github.com/irmen/prog8/releases>`_
#. run the compiler with "java -jar prog8compiler.jar" to see how you can use it.
#. download a recent "fat-jar" (called something like "prog8compiler-all.jar") from `the releases on Github <https://github.com/irmen/prog8/releases>`_
#. run the compiler with "java -jar prog8compiler-all.jar" to see how you can use it.
**Using the shell scripts:**
**using the Gradle build system to make it yourself:**
#. run the "create_compiler_jar.sh" shell script and have a little patience while everything is built
#. it will output "prog8compiler.jar" file which contains everything.
#. run the compiler with "java -jar prog8compiler.jar" to see how you can use it.
The Gradle build system is used to build the compiler.
The most interesting gradle commands to run are probably:
**using the Gradle build system directly:**
``./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, 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/``
``./gradlew shadowDistZip``
Creates a zipfile with the above in it, for easy distribution.
This file can be found in ``./compiler/build/distributions/``
#. run the command "./gradlew installDist" and have a little patience while everything is built
#. it will create the commands and required libraries in the "./compiler/build/install/p8compile/" directory
#. run the compiler with the "./compiler/build/install/p8compile/bin/p8compile" command to see how you can use it.
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
operating systems. If you do have trouble building or running
the compiler on another operating system, please let me know!
@@ -54,14 +74,10 @@ The compiler will link everything together into one output program at the end.
If you start the compiler without arguments, it will print a short usage text.
For normal use the compiler is invoked with the command:
``$ java -jar prog8compiler.jar sourcefile.p8`` if you're using the packaged release version, or
``$ java -jar prog8compiler.jar sourcefile.p8``
``$ ./p8compile.sh sourcefile.p8`` if you're running an unpackaged development version.
If you tell it to save the intermediate code for the prog8 virtual machine, you can
actually run this code with the command:
``$ ./p8vm.sh sourcefile.vm.txt``
Other options are also available, see the introduction page about how
to build and run the compiler.
By default, assembly code is generated and written to ``sourcefile.asm``.
@@ -131,3 +147,24 @@ or::
$ ./p8compile.sh -emu examples/rasterbars.p8
Virtual Machine
---------------
You may have noticed the ``-avm`` and ``-vm`` command line options for the compiler:
-avm
Launches the "AST virtual machine" that directly executes the parsed program.
No compilation steps will be performed.
Allows for very fast testing and debugging before actually compiling programs
to machine code.
It simulates a bare minimum of features from the target platform, so most stuff
that calls ROM routines or writes into hardware registers won't work. But basic
system routines are emulated.
-vm <vm bytecode file>
Launches the "intermediate code VM"
it interprets the intermediate code that the compiler can write when using the ``-writevm``
option. This is the code that will be fed to the assembly code generator,
so you'll skip that last step.
+2 -25
View File
@@ -143,37 +143,14 @@ 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.
The most interesting gradle commands to run are probably:
``./gradlew check``
Builds the compiler code and runs all available checks and unit-tests.
``./gradlew installShadowDist``
Creates a 'fat-jar' that contains the compiler and all dependencies,
and a 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/``
.. note::
Development and testing is done on Linux, but the compiler should run on most
operating systems. If you do have trouble building or running
the compiler on another operating system, please let me know!
Instructions on how to obtain a working compiler are in :ref:`building_compiler`.
.. toctree::
+51 -7
View File
@@ -273,6 +273,39 @@ you have to use the ``str_s`` variants of the string type identifier.
The same is true for arrays by the way.
Structs
^^^^^^^
A struct is a group of one or more other variables.
This allows you to reuse the definition and manipulate it as a whole.
Individual variables in the struct are accessed as you would expect, just
use a scoped name to refer to them: ``structvariable.membername``.
Structs are a bit limited in Prog8: you can only use numerical variables
as member of a struct, so strings and arrays and other structs can not be part of a struct.
Also, it is not possible to use a struct itself inside an array.
Structs are mainly syntactic sugar for repeated groups of vardecls
and assignments that belong together. However, *they are layed out
in sequence in memory as the members are defined* which may be useful
if you want to pass pointers around
To create a variable of a struct type you need to define the struct itself,
and then create a variable with it::
struct Color {
ubyte red
ubyte green
ubyte blue
}
Color rgb = [255,122,0]
Color another ; the init value is optional, like arrays
another = rgb ; assign all of the values of rgb to another
another.blue = 255 ; set a single member
Special types: const and memory-mapped
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -416,15 +449,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
+44 -27
View File
@@ -234,7 +234,7 @@ Various examples::
byte[5] values = 255 ; initialize with five 255 bytes
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
Color rgb = [1,255,0] ; a struct variable
Data types
@@ -358,6 +358,22 @@ Syntax is familiar with brackets: ``arrayvar[x]`` ::
string[4] ; the fifth character (=byte) in the string
Struct
^^^^^^
A *struct* has to be defined to specify what its member variables are.
There are one or more members::
struct <structname> {
<vardecl>
[ <vardecl> ...]
}
You can only use numerical variables as member of a struct, so strings and arrays
and other structs can not be part of a struct. Vice versa, a struct can not occur in an array.
After defining a struct you can use the name of the struct as a data type to declare variables with.
Operators
---------
@@ -444,11 +460,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 +627,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")
}
-19
View File
@@ -52,25 +52,6 @@ Allocate a fixed word in ZP that is the TOS so we can operate on TOS directly
without having to to index into the stack?
structs?
^^^^^^^^
A user defined struct type would be nice to group a bunch
of values together (and use it multiple times). Something like::
struct Point {
ubyte color
word[] vec = [0,0,0]
}
Point p1
Point p2
Point p3
p1.color = 3
p1.vec[2] = 2
Misc
^^^^
+4 -2
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) {
-2
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
}
+8 -9
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
}
}
+12 -8
View File
@@ -5,20 +5,24 @@
const uword width = 40
const uword height = 25
sub start() {
struct Ball {
uword anglex
uword angley
ubyte color
}
sub start() {
Ball ball
while true {
ubyte x = msb(sin8u(msb(anglex)) as uword * width)
ubyte y = msb(cos8u(msb(angley)) as uword * height)
c64scr.setcc(x, y, 81, color)
ubyte x = msb(sin8u(msb(ball.anglex)) as uword * width)
ubyte y = msb(cos8u(msb(ball.angley)) as uword * height)
c64scr.setcc(x, y, 81, ball.color)
anglex+=800
angley+=947
color++
ball.anglex+=800
ball.angley+=947
ball.color++
}
}
}
+100 -93
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()
'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 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()
'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()
}
}
}
+37 -49
View File
@@ -1,56 +1,44 @@
%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")
}
}
struct Color {
uword red
ubyte green
ubyte blue
}
str naam = "irmen"
word[] array = [1,2,3,4]
uword uw = $ab12
Color rgb = [255,128,0]
Color rgb2 = [111,222,33]
ubyte @zp zpvar=99
sub start() {
uword fake_address
fake_address = &naam
c64scr.print_uwhex(1, fake_address)
c64scr.print(", ")
fake_address = &array
c64scr.print_uwhex(1, fake_address)
c64scr.print(", ")
fake_address = &rgb
c64scr.print_uwhex(1, fake_address)
c64scr.print("\n")
; @todo only works once reference types are actually references:
;str name2 = naam ; @todo name2 points to same str as naam
;str name2 = fake_address ; @todo fake_address hopefully points to a str
;Color colz = fake_address ; @todo fake_address hopefully points to a Color
return
}
}
-33
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!"
-9
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
-9
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
-9
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
-9
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
+8 -6
View File
@@ -76,6 +76,7 @@ statement :
| vardecl
| constdecl
| memoryvardecl
| structdecl
| assignment
| augassignment
| unconditionaljump
@@ -109,7 +110,7 @@ directive :
directivearg : stringliteral | identifier | integerliteral ;
vardecl: datatype ZEROPAGE? (arrayindex | ARRAYSIG) ? identifier ;
vardecl: (datatype | structname=identifier) ZEROPAGE? (arrayindex | ARRAYSIG) ? varname=identifier ;
varinitializer : vardecl '=' expression ;
@@ -117,13 +118,13 @@ constdecl: 'const' varinitializer ;
memoryvardecl: ADDRESS_OF varinitializer;
structdecl: 'struct' identifier '{' EOL vardecl ( EOL vardecl)* EOL? '}' EOL;
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 +186,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 +283,5 @@ 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 ) ;
-512
View File
@@ -1,512 +0,0 @@
// Generated from prog8.g4 by ANTLR 4.7.2
package prog8.parser;
import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.atn.*;
import org.antlr.v4.runtime.dfa.DFA;
import org.antlr.v4.runtime.misc.*;
@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
public class prog8Lexer extends Lexer {
static { RuntimeMetaData.checkVersion("4.7.2", RuntimeMetaData.VERSION); }
protected static final DFA[] _decisionToDFA;
protected static final PredictionContextCache _sharedContextCache =
new PredictionContextCache();
public static final int
T__0=1, T__1=2, T__2=3, T__3=4, T__4=5, T__5=6, T__6=7, T__7=8, T__8=9,
T__9=10, T__10=11, T__11=12, T__12=13, T__13=14, T__14=15, T__15=16, T__16=17,
T__17=18, T__18=19, T__19=20, T__20=21, T__21=22, T__22=23, T__23=24,
T__24=25, T__25=26, T__26=27, T__27=28, T__28=29, T__29=30, T__30=31,
T__31=32, T__32=33, T__33=34, T__34=35, T__35=36, T__36=37, T__37=38,
T__38=39, T__39=40, T__40=41, T__41=42, T__42=43, T__43=44, T__44=45,
T__45=46, T__46=47, T__47=48, T__48=49, T__49=50, T__50=51, T__51=52,
T__52=53, T__53=54, T__54=55, T__55=56, T__56=57, T__57=58, T__58=59,
T__59=60, T__60=61, T__61=62, T__62=63, T__63=64, T__64=65, T__65=66,
T__66=67, T__67=68, T__68=69, T__69=70, T__70=71, T__71=72, T__72=73,
T__73=74, T__74=75, T__75=76, T__76=77, T__77=78, T__78=79, T__79=80,
T__80=81, T__81=82, T__82=83, T__83=84, T__84=85, T__85=86, T__86=87,
T__87=88, T__88=89, T__89=90, T__90=91, T__91=92, T__92=93, T__93=94,
T__94=95, T__95=96, T__96=97, T__97=98, T__98=99, T__99=100, T__100=101,
T__101=102, T__102=103, T__103=104, T__104=105, T__105=106, T__106=107,
T__107=108, T__108=109, LINECOMMENT=110, COMMENT=111, WS=112, EOL=113,
NAME=114, DEC_INTEGER=115, HEX_INTEGER=116, BIN_INTEGER=117, ADDRESS_OF=118,
FLOAT_NUMBER=119, STRING=120, INLINEASMBLOCK=121, SINGLECHAR=122, ZEROPAGE=123,
ARRAYSIG=124;
public static String[] channelNames = {
"DEFAULT_TOKEN_CHANNEL", "HIDDEN"
};
public static String[] modeNames = {
"DEFAULT_MODE"
};
private static String[] makeRuleNames() {
return new String[] {
"T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", "T__7", "T__8",
"T__9", "T__10", "T__11", "T__12", "T__13", "T__14", "T__15", "T__16",
"T__17", "T__18", "T__19", "T__20", "T__21", "T__22", "T__23", "T__24",
"T__25", "T__26", "T__27", "T__28", "T__29", "T__30", "T__31", "T__32",
"T__33", "T__34", "T__35", "T__36", "T__37", "T__38", "T__39", "T__40",
"T__41", "T__42", "T__43", "T__44", "T__45", "T__46", "T__47", "T__48",
"T__49", "T__50", "T__51", "T__52", "T__53", "T__54", "T__55", "T__56",
"T__57", "T__58", "T__59", "T__60", "T__61", "T__62", "T__63", "T__64",
"T__65", "T__66", "T__67", "T__68", "T__69", "T__70", "T__71", "T__72",
"T__73", "T__74", "T__75", "T__76", "T__77", "T__78", "T__79", "T__80",
"T__81", "T__82", "T__83", "T__84", "T__85", "T__86", "T__87", "T__88",
"T__89", "T__90", "T__91", "T__92", "T__93", "T__94", "T__95", "T__96",
"T__97", "T__98", "T__99", "T__100", "T__101", "T__102", "T__103", "T__104",
"T__105", "T__106", "T__107", "T__108", "LINECOMMENT", "COMMENT", "WS",
"EOL", "NAME", "DEC_INTEGER", "HEX_INTEGER", "BIN_INTEGER", "ADDRESS_OF",
"FLOAT_NUMBER", "FNUMBER", "STRING_ESCAPE_SEQ", "STRING", "INLINEASMBLOCK",
"SINGLECHAR", "ZEROPAGE", "ARRAYSIG"
};
}
public static final String[] ruleNames = makeRuleNames();
private static String[] makeLiteralNames() {
return new String[] {
null, "'~'", "':'", "'goto'", "'%output'", "'%launcher'", "'%zeropage'",
"'%zpreserved'", "'%address'", "'%import'", "'%breakpoint'", "'%asminclude'",
"'%asmbinary'", "'%option'", "','", "'='", "'const'", "'ubyte'", "'byte'",
"'uword'", "'word'", "'float'", "'str'", "'str_s'", "'['", "']'", "'+='",
"'-='", "'/='", "'*='", "'**='", "'&='", "'|='", "'^='", "'%='", "'<<='",
"'>>='", "'++'", "'--'", "'+'", "'-'", "'**'", "'*'", "'/'", "'%'", "'<<'",
"'>>'", "'<'", "'>'", "'<='", "'>='", "'=='", "'!='", "'^'", "'|'", "'to'",
"'step'", "'and'", "'or'", "'xor'", "'not'", "'('", "')'", "'as'", "'@'",
"'return'", "'break'", "'continue'", "'.'", "'A'", "'X'", "'Y'", "'AX'",
"'AY'", "'XY'", "'Pc'", "'Pz'", "'Pn'", "'Pv'", "'.w'", "'true'", "'false'",
"'%asm'", "'sub'", "'->'", "'{'", "'}'", "'asmsub'", "'stack'", "'clobbers'",
"'if'", "'else'", "'if_cs'", "'if_cc'", "'if_eq'", "'if_z'", "'if_ne'",
"'if_nz'", "'if_pl'", "'if_pos'", "'if_mi'", "'if_neg'", "'if_vs'", "'if_vc'",
"'for'", "'in'", "'while'", "'repeat'", "'until'", "'when'", null, null,
null, null, null, null, null, null, "'&'", null, null, null, null, "'@zp'",
"'[]'"
};
}
private static final String[] _LITERAL_NAMES = makeLiteralNames();
private static String[] makeSymbolicNames() {
return new String[] {
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, "LINECOMMENT", "COMMENT", "WS", "EOL", "NAME", "DEC_INTEGER",
"HEX_INTEGER", "BIN_INTEGER", "ADDRESS_OF", "FLOAT_NUMBER", "STRING",
"INLINEASMBLOCK", "SINGLECHAR", "ZEROPAGE", "ARRAYSIG"
};
}
private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames();
public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES);
/**
* @deprecated Use {@link #VOCABULARY} instead.
*/
@Deprecated
public static final String[] tokenNames;
static {
tokenNames = new String[_SYMBOLIC_NAMES.length];
for (int i = 0; i < tokenNames.length; i++) {
tokenNames[i] = VOCABULARY.getLiteralName(i);
if (tokenNames[i] == null) {
tokenNames[i] = VOCABULARY.getSymbolicName(i);
}
if (tokenNames[i] == null) {
tokenNames[i] = "<INVALID>";
}
}
}
@Override
@Deprecated
public String[] getTokenNames() {
return tokenNames;
}
@Override
public Vocabulary getVocabulary() {
return VOCABULARY;
}
public prog8Lexer(CharStream input) {
super(input);
_interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache);
}
@Override
public String getGrammarFileName() { return "prog8.g4"; }
@Override
public String[] getRuleNames() { return ruleNames; }
@Override
public String getSerializedATN() { return _serializedATN; }
@Override
public String[] getChannelNames() { return channelNames; }
@Override
public String[] getModeNames() { return modeNames; }
@Override
public ATN getATN() { return _ATN; }
@Override
public void action(RuleContext _localctx, int ruleIndex, int actionIndex) {
switch (ruleIndex) {
case 121:
STRING_action((RuleContext)_localctx, actionIndex);
break;
case 122:
INLINEASMBLOCK_action((RuleContext)_localctx, actionIndex);
break;
case 123:
SINGLECHAR_action((RuleContext)_localctx, actionIndex);
break;
}
}
private void STRING_action(RuleContext _localctx, int actionIndex) {
switch (actionIndex) {
case 0:
// get rid of the enclosing quotes
String s = getText();
setText(s.substring(1, s.length() - 1));
break;
}
}
private void INLINEASMBLOCK_action(RuleContext _localctx, int actionIndex) {
switch (actionIndex) {
case 1:
// get rid of the enclosing double braces
String s = getText();
setText(s.substring(2, s.length() - 2));
break;
}
}
private void SINGLECHAR_action(RuleContext _localctx, int actionIndex) {
switch (actionIndex) {
case 2:
// get rid of the enclosing quotes
String s = getText();
setText(s.substring(1, s.length() - 1));
break;
}
}
public static final String _serializedATN =
"\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2~\u0365\b\1\4\2\t"+
"\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13"+
"\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22"+
"\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27\4\30\t\30\4\31\t\31"+
"\4\32\t\32\4\33\t\33\4\34\t\34\4\35\t\35\4\36\t\36\4\37\t\37\4 \t \4!"+
"\t!\4\"\t\"\4#\t#\4$\t$\4%\t%\4&\t&\4\'\t\'\4(\t(\4)\t)\4*\t*\4+\t+\4"+
",\t,\4-\t-\4.\t.\4/\t/\4\60\t\60\4\61\t\61\4\62\t\62\4\63\t\63\4\64\t"+
"\64\4\65\t\65\4\66\t\66\4\67\t\67\48\t8\49\t9\4:\t:\4;\t;\4<\t<\4=\t="+
"\4>\t>\4?\t?\4@\t@\4A\tA\4B\tB\4C\tC\4D\tD\4E\tE\4F\tF\4G\tG\4H\tH\4I"+
"\tI\4J\tJ\4K\tK\4L\tL\4M\tM\4N\tN\4O\tO\4P\tP\4Q\tQ\4R\tR\4S\tS\4T\tT"+
"\4U\tU\4V\tV\4W\tW\4X\tX\4Y\tY\4Z\tZ\4[\t[\4\\\t\\\4]\t]\4^\t^\4_\t_\4"+
"`\t`\4a\ta\4b\tb\4c\tc\4d\td\4e\te\4f\tf\4g\tg\4h\th\4i\ti\4j\tj\4k\t"+
"k\4l\tl\4m\tm\4n\tn\4o\to\4p\tp\4q\tq\4r\tr\4s\ts\4t\tt\4u\tu\4v\tv\4"+
"w\tw\4x\tx\4y\ty\4z\tz\4{\t{\4|\t|\4}\t}\4~\t~\4\177\t\177\3\2\3\2\3\3"+
"\3\3\3\4\3\4\3\4\3\4\3\4\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\6\3\6\3\6\3"+
"\6\3\6\3\6\3\6\3\6\3\6\3\6\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\b"+
"\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\t\3\t\3\t\3\t\3\t\3\t\3"+
"\t\3\t\3\t\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\13\3\13\3\13\3\13\3\13\3"+
"\13\3\13\3\13\3\13\3\13\3\13\3\13\3\f\3\f\3\f\3\f\3\f\3\f\3\f\3\f\3\f"+
"\3\f\3\f\3\f\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\16\3\16\3\16"+
"\3\16\3\16\3\16\3\16\3\16\3\17\3\17\3\20\3\20\3\21\3\21\3\21\3\21\3\21"+
"\3\21\3\22\3\22\3\22\3\22\3\22\3\22\3\23\3\23\3\23\3\23\3\23\3\24\3\24"+
"\3\24\3\24\3\24\3\24\3\25\3\25\3\25\3\25\3\25\3\26\3\26\3\26\3\26\3\26"+
"\3\26\3\27\3\27\3\27\3\27\3\30\3\30\3\30\3\30\3\30\3\30\3\31\3\31\3\32"+
"\3\32\3\33\3\33\3\33\3\34\3\34\3\34\3\35\3\35\3\35\3\36\3\36\3\36\3\37"+
"\3\37\3\37\3\37\3 \3 \3 \3!\3!\3!\3\"\3\"\3\"\3#\3#\3#\3$\3$\3$\3$\3%"+
"\3%\3%\3%\3&\3&\3&\3\'\3\'\3\'\3(\3(\3)\3)\3*\3*\3*\3+\3+\3,\3,\3-\3-"+
"\3.\3.\3.\3/\3/\3/\3\60\3\60\3\61\3\61\3\62\3\62\3\62\3\63\3\63\3\63\3"+
"\64\3\64\3\64\3\65\3\65\3\65\3\66\3\66\3\67\3\67\38\38\38\39\39\39\39"+
"\39\3:\3:\3:\3:\3;\3;\3;\3<\3<\3<\3<\3=\3=\3=\3=\3>\3>\3?\3?\3@\3@\3@"+
"\3A\3A\3B\3B\3B\3B\3B\3B\3B\3C\3C\3C\3C\3C\3C\3D\3D\3D\3D\3D\3D\3D\3D"+
"\3D\3E\3E\3F\3F\3G\3G\3H\3H\3I\3I\3I\3J\3J\3J\3K\3K\3K\3L\3L\3L\3M\3M"+
"\3M\3N\3N\3N\3O\3O\3O\3P\3P\3P\3Q\3Q\3Q\3Q\3Q\3R\3R\3R\3R\3R\3R\3S\3S"+
"\3S\3S\3S\3T\3T\3T\3T\3U\3U\3U\3V\3V\3W\3W\3X\3X\3X\3X\3X\3X\3X\3Y\3Y"+
"\3Y\3Y\3Y\3Y\3Z\3Z\3Z\3Z\3Z\3Z\3Z\3Z\3Z\3[\3[\3[\3\\\3\\\3\\\3\\\3\\\3"+
"]\3]\3]\3]\3]\3]\3^\3^\3^\3^\3^\3^\3_\3_\3_\3_\3_\3_\3`\3`\3`\3`\3`\3"+
"a\3a\3a\3a\3a\3a\3b\3b\3b\3b\3b\3b\3c\3c\3c\3c\3c\3c\3d\3d\3d\3d\3d\3"+
"d\3d\3e\3e\3e\3e\3e\3e\3f\3f\3f\3f\3f\3f\3f\3g\3g\3g\3g\3g\3g\3h\3h\3"+
"h\3h\3h\3h\3i\3i\3i\3i\3j\3j\3j\3k\3k\3k\3k\3k\3k\3l\3l\3l\3l\3l\3l\3"+
"l\3m\3m\3m\3m\3m\3m\3n\3n\3n\3n\3n\3o\3o\7o\u02eb\no\fo\16o\u02ee\13o"+
"\3o\3o\3o\3o\3p\3p\7p\u02f6\np\fp\16p\u02f9\13p\3p\3p\3q\3q\3q\3q\3r\6"+
"r\u0302\nr\rr\16r\u0303\3s\3s\7s\u0308\ns\fs\16s\u030b\13s\3t\3t\3t\6"+
"t\u0310\nt\rt\16t\u0311\5t\u0314\nt\3u\3u\6u\u0318\nu\ru\16u\u0319\3v"+
"\3v\6v\u031e\nv\rv\16v\u031f\3w\3w\3x\3x\3x\5x\u0327\nx\3x\5x\u032a\n"+
"x\3y\6y\u032d\ny\ry\16y\u032e\3y\3y\6y\u0333\ny\ry\16y\u0334\5y\u0337"+
"\ny\3z\3z\3z\3z\5z\u033d\nz\3{\3{\3{\7{\u0342\n{\f{\16{\u0345\13{\3{\3"+
"{\3{\3|\3|\3|\3|\6|\u034e\n|\r|\16|\u034f\3|\3|\3|\3|\3|\3}\3}\3}\5}\u035a"+
"\n}\3}\3}\3}\3~\3~\3~\3~\3\177\3\177\3\177\3\u034f\2\u0080\3\3\5\4\7\5"+
"\t\6\13\7\r\b\17\t\21\n\23\13\25\f\27\r\31\16\33\17\35\20\37\21!\22#\23"+
"%\24\'\25)\26+\27-\30/\31\61\32\63\33\65\34\67\359\36;\37= ?!A\"C#E$G"+
"%I&K\'M(O)Q*S+U,W-Y.[/]\60_\61a\62c\63e\64g\65i\66k\67m8o9q:s;u<w=y>{"+
"?}@\177A\u0081B\u0083C\u0085D\u0087E\u0089F\u008bG\u008dH\u008fI\u0091"+
"J\u0093K\u0095L\u0097M\u0099N\u009bO\u009dP\u009fQ\u00a1R\u00a3S\u00a5"+
"T\u00a7U\u00a9V\u00abW\u00adX\u00afY\u00b1Z\u00b3[\u00b5\\\u00b7]\u00b9"+
"^\u00bb_\u00bd`\u00bfa\u00c1b\u00c3c\u00c5d\u00c7e\u00c9f\u00cbg\u00cd"+
"h\u00cfi\u00d1j\u00d3k\u00d5l\u00d7m\u00d9n\u00dbo\u00ddp\u00dfq\u00e1"+
"r\u00e3s\u00e5t\u00e7u\u00e9v\u00ebw\u00edx\u00efy\u00f1\2\u00f3\2\u00f5"+
"z\u00f7{\u00f9|\u00fb}\u00fd~\3\2\n\4\2\f\f\17\17\4\2\13\13\"\"\5\2C\\"+
"aac|\6\2\62;C\\aac|\5\2\62;CHch\4\2GGgg\4\2--//\6\2\f\f\16\17$$^^\2\u0374"+
"\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2\2\t\3\2\2\2\2\13\3\2\2\2\2\r\3\2"+
"\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2\2\25\3\2\2\2\2\27\3\2\2\2"+
"\2\31\3\2\2\2\2\33\3\2\2\2\2\35\3\2\2\2\2\37\3\2\2\2\2!\3\2\2\2\2#\3\2"+
"\2\2\2%\3\2\2\2\2\'\3\2\2\2\2)\3\2\2\2\2+\3\2\2\2\2-\3\2\2\2\2/\3\2\2"+
"\2\2\61\3\2\2\2\2\63\3\2\2\2\2\65\3\2\2\2\2\67\3\2\2\2\29\3\2\2\2\2;\3"+
"\2\2\2\2=\3\2\2\2\2?\3\2\2\2\2A\3\2\2\2\2C\3\2\2\2\2E\3\2\2\2\2G\3\2\2"+
"\2\2I\3\2\2\2\2K\3\2\2\2\2M\3\2\2\2\2O\3\2\2\2\2Q\3\2\2\2\2S\3\2\2\2\2"+
"U\3\2\2\2\2W\3\2\2\2\2Y\3\2\2\2\2[\3\2\2\2\2]\3\2\2\2\2_\3\2\2\2\2a\3"+
"\2\2\2\2c\3\2\2\2\2e\3\2\2\2\2g\3\2\2\2\2i\3\2\2\2\2k\3\2\2\2\2m\3\2\2"+
"\2\2o\3\2\2\2\2q\3\2\2\2\2s\3\2\2\2\2u\3\2\2\2\2w\3\2\2\2\2y\3\2\2\2\2"+
"{\3\2\2\2\2}\3\2\2\2\2\177\3\2\2\2\2\u0081\3\2\2\2\2\u0083\3\2\2\2\2\u0085"+
"\3\2\2\2\2\u0087\3\2\2\2\2\u0089\3\2\2\2\2\u008b\3\2\2\2\2\u008d\3\2\2"+
"\2\2\u008f\3\2\2\2\2\u0091\3\2\2\2\2\u0093\3\2\2\2\2\u0095\3\2\2\2\2\u0097"+
"\3\2\2\2\2\u0099\3\2\2\2\2\u009b\3\2\2\2\2\u009d\3\2\2\2\2\u009f\3\2\2"+
"\2\2\u00a1\3\2\2\2\2\u00a3\3\2\2\2\2\u00a5\3\2\2\2\2\u00a7\3\2\2\2\2\u00a9"+
"\3\2\2\2\2\u00ab\3\2\2\2\2\u00ad\3\2\2\2\2\u00af\3\2\2\2\2\u00b1\3\2\2"+
"\2\2\u00b3\3\2\2\2\2\u00b5\3\2\2\2\2\u00b7\3\2\2\2\2\u00b9\3\2\2\2\2\u00bb"+
"\3\2\2\2\2\u00bd\3\2\2\2\2\u00bf\3\2\2\2\2\u00c1\3\2\2\2\2\u00c3\3\2\2"+
"\2\2\u00c5\3\2\2\2\2\u00c7\3\2\2\2\2\u00c9\3\2\2\2\2\u00cb\3\2\2\2\2\u00cd"+
"\3\2\2\2\2\u00cf\3\2\2\2\2\u00d1\3\2\2\2\2\u00d3\3\2\2\2\2\u00d5\3\2\2"+
"\2\2\u00d7\3\2\2\2\2\u00d9\3\2\2\2\2\u00db\3\2\2\2\2\u00dd\3\2\2\2\2\u00df"+
"\3\2\2\2\2\u00e1\3\2\2\2\2\u00e3\3\2\2\2\2\u00e5\3\2\2\2\2\u00e7\3\2\2"+
"\2\2\u00e9\3\2\2\2\2\u00eb\3\2\2\2\2\u00ed\3\2\2\2\2\u00ef\3\2\2\2\2\u00f5"+
"\3\2\2\2\2\u00f7\3\2\2\2\2\u00f9\3\2\2\2\2\u00fb\3\2\2\2\2\u00fd\3\2\2"+
"\2\3\u00ff\3\2\2\2\5\u0101\3\2\2\2\7\u0103\3\2\2\2\t\u0108\3\2\2\2\13"+
"\u0110\3\2\2\2\r\u011a\3\2\2\2\17\u0124\3\2\2\2\21\u0130\3\2\2\2\23\u0139"+
"\3\2\2\2\25\u0141\3\2\2\2\27\u014d\3\2\2\2\31\u0159\3\2\2\2\33\u0164\3"+
"\2\2\2\35\u016c\3\2\2\2\37\u016e\3\2\2\2!\u0170\3\2\2\2#\u0176\3\2\2\2"+
"%\u017c\3\2\2\2\'\u0181\3\2\2\2)\u0187\3\2\2\2+\u018c\3\2\2\2-\u0192\3"+
"\2\2\2/\u0196\3\2\2\2\61\u019c\3\2\2\2\63\u019e\3\2\2\2\65\u01a0\3\2\2"+
"\2\67\u01a3\3\2\2\29\u01a6\3\2\2\2;\u01a9\3\2\2\2=\u01ac\3\2\2\2?\u01b0"+
"\3\2\2\2A\u01b3\3\2\2\2C\u01b6\3\2\2\2E\u01b9\3\2\2\2G\u01bc\3\2\2\2I"+
"\u01c0\3\2\2\2K\u01c4\3\2\2\2M\u01c7\3\2\2\2O\u01ca\3\2\2\2Q\u01cc\3\2"+
"\2\2S\u01ce\3\2\2\2U\u01d1\3\2\2\2W\u01d3\3\2\2\2Y\u01d5\3\2\2\2[\u01d7"+
"\3\2\2\2]\u01da\3\2\2\2_\u01dd\3\2\2\2a\u01df\3\2\2\2c\u01e1\3\2\2\2e"+
"\u01e4\3\2\2\2g\u01e7\3\2\2\2i\u01ea\3\2\2\2k\u01ed\3\2\2\2m\u01ef\3\2"+
"\2\2o\u01f1\3\2\2\2q\u01f4\3\2\2\2s\u01f9\3\2\2\2u\u01fd\3\2\2\2w\u0200"+
"\3\2\2\2y\u0204\3\2\2\2{\u0208\3\2\2\2}\u020a\3\2\2\2\177\u020c\3\2\2"+
"\2\u0081\u020f\3\2\2\2\u0083\u0211\3\2\2\2\u0085\u0218\3\2\2\2\u0087\u021e"+
"\3\2\2\2\u0089\u0227\3\2\2\2\u008b\u0229\3\2\2\2\u008d\u022b\3\2\2\2\u008f"+
"\u022d\3\2\2\2\u0091\u022f\3\2\2\2\u0093\u0232\3\2\2\2\u0095\u0235\3\2"+
"\2\2\u0097\u0238\3\2\2\2\u0099\u023b\3\2\2\2\u009b\u023e\3\2\2\2\u009d"+
"\u0241\3\2\2\2\u009f\u0244\3\2\2\2\u00a1\u0247\3\2\2\2\u00a3\u024c\3\2"+
"\2\2\u00a5\u0252\3\2\2\2\u00a7\u0257\3\2\2\2\u00a9\u025b\3\2\2\2\u00ab"+
"\u025e\3\2\2\2\u00ad\u0260\3\2\2\2\u00af\u0262\3\2\2\2\u00b1\u0269\3\2"+
"\2\2\u00b3\u026f\3\2\2\2\u00b5\u0278\3\2\2\2\u00b7\u027b\3\2\2\2\u00b9"+
"\u0280\3\2\2\2\u00bb\u0286\3\2\2\2\u00bd\u028c\3\2\2\2\u00bf\u0292\3\2"+
"\2\2\u00c1\u0297\3\2\2\2\u00c3\u029d\3\2\2\2\u00c5\u02a3\3\2\2\2\u00c7"+
"\u02a9\3\2\2\2\u00c9\u02b0\3\2\2\2\u00cb\u02b6\3\2\2\2\u00cd\u02bd\3\2"+
"\2\2\u00cf\u02c3\3\2\2\2\u00d1\u02c9\3\2\2\2\u00d3\u02cd\3\2\2\2\u00d5"+
"\u02d0\3\2\2\2\u00d7\u02d6\3\2\2\2\u00d9\u02dd\3\2\2\2\u00db\u02e3\3\2"+
"\2\2\u00dd\u02e8\3\2\2\2\u00df\u02f3\3\2\2\2\u00e1\u02fc\3\2\2\2\u00e3"+
"\u0301\3\2\2\2\u00e5\u0305\3\2\2\2\u00e7\u0313\3\2\2\2\u00e9\u0315\3\2"+
"\2\2\u00eb\u031b\3\2\2\2\u00ed\u0321\3\2\2\2\u00ef\u0323\3\2\2\2\u00f1"+
"\u032c\3\2\2\2\u00f3\u033c\3\2\2\2\u00f5\u033e\3\2\2\2\u00f7\u0349\3\2"+
"\2\2\u00f9\u0356\3\2\2\2\u00fb\u035e\3\2\2\2\u00fd\u0362\3\2\2\2\u00ff"+
"\u0100\7\u0080\2\2\u0100\4\3\2\2\2\u0101\u0102\7<\2\2\u0102\6\3\2\2\2"+
"\u0103\u0104\7i\2\2\u0104\u0105\7q\2\2\u0105\u0106\7v\2\2\u0106\u0107"+
"\7q\2\2\u0107\b\3\2\2\2\u0108\u0109\7\'\2\2\u0109\u010a\7q\2\2\u010a\u010b"+
"\7w\2\2\u010b\u010c\7v\2\2\u010c\u010d\7r\2\2\u010d\u010e\7w\2\2\u010e"+
"\u010f\7v\2\2\u010f\n\3\2\2\2\u0110\u0111\7\'\2\2\u0111\u0112\7n\2\2\u0112"+
"\u0113\7c\2\2\u0113\u0114\7w\2\2\u0114\u0115\7p\2\2\u0115\u0116\7e\2\2"+
"\u0116\u0117\7j\2\2\u0117\u0118\7g\2\2\u0118\u0119\7t\2\2\u0119\f\3\2"+
"\2\2\u011a\u011b\7\'\2\2\u011b\u011c\7|\2\2\u011c\u011d\7g\2\2\u011d\u011e"+
"\7t\2\2\u011e\u011f\7q\2\2\u011f\u0120\7r\2\2\u0120\u0121\7c\2\2\u0121"+
"\u0122\7i\2\2\u0122\u0123\7g\2\2\u0123\16\3\2\2\2\u0124\u0125\7\'\2\2"+
"\u0125\u0126\7|\2\2\u0126\u0127\7r\2\2\u0127\u0128\7t\2\2\u0128\u0129"+
"\7g\2\2\u0129\u012a\7u\2\2\u012a\u012b\7g\2\2\u012b\u012c\7t\2\2\u012c"+
"\u012d\7x\2\2\u012d\u012e\7g\2\2\u012e\u012f\7f\2\2\u012f\20\3\2\2\2\u0130"+
"\u0131\7\'\2\2\u0131\u0132\7c\2\2\u0132\u0133\7f\2\2\u0133\u0134\7f\2"+
"\2\u0134\u0135\7t\2\2\u0135\u0136\7g\2\2\u0136\u0137\7u\2\2\u0137\u0138"+
"\7u\2\2\u0138\22\3\2\2\2\u0139\u013a\7\'\2\2\u013a\u013b\7k\2\2\u013b"+
"\u013c\7o\2\2\u013c\u013d\7r\2\2\u013d\u013e\7q\2\2\u013e\u013f\7t\2\2"+
"\u013f\u0140\7v\2\2\u0140\24\3\2\2\2\u0141\u0142\7\'\2\2\u0142\u0143\7"+
"d\2\2\u0143\u0144\7t\2\2\u0144\u0145\7g\2\2\u0145\u0146\7c\2\2\u0146\u0147"+
"\7m\2\2\u0147\u0148\7r\2\2\u0148\u0149\7q\2\2\u0149\u014a\7k\2\2\u014a"+
"\u014b\7p\2\2\u014b\u014c\7v\2\2\u014c\26\3\2\2\2\u014d\u014e\7\'\2\2"+
"\u014e\u014f\7c\2\2\u014f\u0150\7u\2\2\u0150\u0151\7o\2\2\u0151\u0152"+
"\7k\2\2\u0152\u0153\7p\2\2\u0153\u0154\7e\2\2\u0154\u0155\7n\2\2\u0155"+
"\u0156\7w\2\2\u0156\u0157\7f\2\2\u0157\u0158\7g\2\2\u0158\30\3\2\2\2\u0159"+
"\u015a\7\'\2\2\u015a\u015b\7c\2\2\u015b\u015c\7u\2\2\u015c\u015d\7o\2"+
"\2\u015d\u015e\7d\2\2\u015e\u015f\7k\2\2\u015f\u0160\7p\2\2\u0160\u0161"+
"\7c\2\2\u0161\u0162\7t\2\2\u0162\u0163\7{\2\2\u0163\32\3\2\2\2\u0164\u0165"+
"\7\'\2\2\u0165\u0166\7q\2\2\u0166\u0167\7r\2\2\u0167\u0168\7v\2\2\u0168"+
"\u0169\7k\2\2\u0169\u016a\7q\2\2\u016a\u016b\7p\2\2\u016b\34\3\2\2\2\u016c"+
"\u016d\7.\2\2\u016d\36\3\2\2\2\u016e\u016f\7?\2\2\u016f \3\2\2\2\u0170"+
"\u0171\7e\2\2\u0171\u0172\7q\2\2\u0172\u0173\7p\2\2\u0173\u0174\7u\2\2"+
"\u0174\u0175\7v\2\2\u0175\"\3\2\2\2\u0176\u0177\7w\2\2\u0177\u0178\7d"+
"\2\2\u0178\u0179\7{\2\2\u0179\u017a\7v\2\2\u017a\u017b\7g\2\2\u017b$\3"+
"\2\2\2\u017c\u017d\7d\2\2\u017d\u017e\7{\2\2\u017e\u017f\7v\2\2\u017f"+
"\u0180\7g\2\2\u0180&\3\2\2\2\u0181\u0182\7w\2\2\u0182\u0183\7y\2\2\u0183"+
"\u0184\7q\2\2\u0184\u0185\7t\2\2\u0185\u0186\7f\2\2\u0186(\3\2\2\2\u0187"+
"\u0188\7y\2\2\u0188\u0189\7q\2\2\u0189\u018a\7t\2\2\u018a\u018b\7f\2\2"+
"\u018b*\3\2\2\2\u018c\u018d\7h\2\2\u018d\u018e\7n\2\2\u018e\u018f\7q\2"+
"\2\u018f\u0190\7c\2\2\u0190\u0191\7v\2\2\u0191,\3\2\2\2\u0192\u0193\7"+
"u\2\2\u0193\u0194\7v\2\2\u0194\u0195\7t\2\2\u0195.\3\2\2\2\u0196\u0197"+
"\7u\2\2\u0197\u0198\7v\2\2\u0198\u0199\7t\2\2\u0199\u019a\7a\2\2\u019a"+
"\u019b\7u\2\2\u019b\60\3\2\2\2\u019c\u019d\7]\2\2\u019d\62\3\2\2\2\u019e"+
"\u019f\7_\2\2\u019f\64\3\2\2\2\u01a0\u01a1\7-\2\2\u01a1\u01a2\7?\2\2\u01a2"+
"\66\3\2\2\2\u01a3\u01a4\7/\2\2\u01a4\u01a5\7?\2\2\u01a58\3\2\2\2\u01a6"+
"\u01a7\7\61\2\2\u01a7\u01a8\7?\2\2\u01a8:\3\2\2\2\u01a9\u01aa\7,\2\2\u01aa"+
"\u01ab\7?\2\2\u01ab<\3\2\2\2\u01ac\u01ad\7,\2\2\u01ad\u01ae\7,\2\2\u01ae"+
"\u01af\7?\2\2\u01af>\3\2\2\2\u01b0\u01b1\7(\2\2\u01b1\u01b2\7?\2\2\u01b2"+
"@\3\2\2\2\u01b3\u01b4\7~\2\2\u01b4\u01b5\7?\2\2\u01b5B\3\2\2\2\u01b6\u01b7"+
"\7`\2\2\u01b7\u01b8\7?\2\2\u01b8D\3\2\2\2\u01b9\u01ba\7\'\2\2\u01ba\u01bb"+
"\7?\2\2\u01bbF\3\2\2\2\u01bc\u01bd\7>\2\2\u01bd\u01be\7>\2\2\u01be\u01bf"+
"\7?\2\2\u01bfH\3\2\2\2\u01c0\u01c1\7@\2\2\u01c1\u01c2\7@\2\2\u01c2\u01c3"+
"\7?\2\2\u01c3J\3\2\2\2\u01c4\u01c5\7-\2\2\u01c5\u01c6\7-\2\2\u01c6L\3"+
"\2\2\2\u01c7\u01c8\7/\2\2\u01c8\u01c9\7/\2\2\u01c9N\3\2\2\2\u01ca\u01cb"+
"\7-\2\2\u01cbP\3\2\2\2\u01cc\u01cd\7/\2\2\u01cdR\3\2\2\2\u01ce\u01cf\7"+
",\2\2\u01cf\u01d0\7,\2\2\u01d0T\3\2\2\2\u01d1\u01d2\7,\2\2\u01d2V\3\2"+
"\2\2\u01d3\u01d4\7\61\2\2\u01d4X\3\2\2\2\u01d5\u01d6\7\'\2\2\u01d6Z\3"+
"\2\2\2\u01d7\u01d8\7>\2\2\u01d8\u01d9\7>\2\2\u01d9\\\3\2\2\2\u01da\u01db"+
"\7@\2\2\u01db\u01dc\7@\2\2\u01dc^\3\2\2\2\u01dd\u01de\7>\2\2\u01de`\3"+
"\2\2\2\u01df\u01e0\7@\2\2\u01e0b\3\2\2\2\u01e1\u01e2\7>\2\2\u01e2\u01e3"+
"\7?\2\2\u01e3d\3\2\2\2\u01e4\u01e5\7@\2\2\u01e5\u01e6\7?\2\2\u01e6f\3"+
"\2\2\2\u01e7\u01e8\7?\2\2\u01e8\u01e9\7?\2\2\u01e9h\3\2\2\2\u01ea\u01eb"+
"\7#\2\2\u01eb\u01ec\7?\2\2\u01ecj\3\2\2\2\u01ed\u01ee\7`\2\2\u01eel\3"+
"\2\2\2\u01ef\u01f0\7~\2\2\u01f0n\3\2\2\2\u01f1\u01f2\7v\2\2\u01f2\u01f3"+
"\7q\2\2\u01f3p\3\2\2\2\u01f4\u01f5\7u\2\2\u01f5\u01f6\7v\2\2\u01f6\u01f7"+
"\7g\2\2\u01f7\u01f8\7r\2\2\u01f8r\3\2\2\2\u01f9\u01fa\7c\2\2\u01fa\u01fb"+
"\7p\2\2\u01fb\u01fc\7f\2\2\u01fct\3\2\2\2\u01fd\u01fe\7q\2\2\u01fe\u01ff"+
"\7t\2\2\u01ffv\3\2\2\2\u0200\u0201\7z\2\2\u0201\u0202\7q\2\2\u0202\u0203"+
"\7t\2\2\u0203x\3\2\2\2\u0204\u0205\7p\2\2\u0205\u0206\7q\2\2\u0206\u0207"+
"\7v\2\2\u0207z\3\2\2\2\u0208\u0209\7*\2\2\u0209|\3\2\2\2\u020a\u020b\7"+
"+\2\2\u020b~\3\2\2\2\u020c\u020d\7c\2\2\u020d\u020e\7u\2\2\u020e\u0080"+
"\3\2\2\2\u020f\u0210\7B\2\2\u0210\u0082\3\2\2\2\u0211\u0212\7t\2\2\u0212"+
"\u0213\7g\2\2\u0213\u0214\7v\2\2\u0214\u0215\7w\2\2\u0215\u0216\7t\2\2"+
"\u0216\u0217\7p\2\2\u0217\u0084\3\2\2\2\u0218\u0219\7d\2\2\u0219\u021a"+
"\7t\2\2\u021a\u021b\7g\2\2\u021b\u021c\7c\2\2\u021c\u021d\7m\2\2\u021d"+
"\u0086\3\2\2\2\u021e\u021f\7e\2\2\u021f\u0220\7q\2\2\u0220\u0221\7p\2"+
"\2\u0221\u0222\7v\2\2\u0222\u0223\7k\2\2\u0223\u0224\7p\2\2\u0224\u0225"+
"\7w\2\2\u0225\u0226\7g\2\2\u0226\u0088\3\2\2\2\u0227\u0228\7\60\2\2\u0228"+
"\u008a\3\2\2\2\u0229\u022a\7C\2\2\u022a\u008c\3\2\2\2\u022b\u022c\7Z\2"+
"\2\u022c\u008e\3\2\2\2\u022d\u022e\7[\2\2\u022e\u0090\3\2\2\2\u022f\u0230"+
"\7C\2\2\u0230\u0231\7Z\2\2\u0231\u0092\3\2\2\2\u0232\u0233\7C\2\2\u0233"+
"\u0234\7[\2\2\u0234\u0094\3\2\2\2\u0235\u0236\7Z\2\2\u0236\u0237\7[\2"+
"\2\u0237\u0096\3\2\2\2\u0238\u0239\7R\2\2\u0239\u023a\7e\2\2\u023a\u0098"+
"\3\2\2\2\u023b\u023c\7R\2\2\u023c\u023d\7|\2\2\u023d\u009a\3\2\2\2\u023e"+
"\u023f\7R\2\2\u023f\u0240\7p\2\2\u0240\u009c\3\2\2\2\u0241\u0242\7R\2"+
"\2\u0242\u0243\7x\2\2\u0243\u009e\3\2\2\2\u0244\u0245\7\60\2\2\u0245\u0246"+
"\7y\2\2\u0246\u00a0\3\2\2\2\u0247\u0248\7v\2\2\u0248\u0249\7t\2\2\u0249"+
"\u024a\7w\2\2\u024a\u024b\7g\2\2\u024b\u00a2\3\2\2\2\u024c\u024d\7h\2"+
"\2\u024d\u024e\7c\2\2\u024e\u024f\7n\2\2\u024f\u0250\7u\2\2\u0250\u0251"+
"\7g\2\2\u0251\u00a4\3\2\2\2\u0252\u0253\7\'\2\2\u0253\u0254\7c\2\2\u0254"+
"\u0255\7u\2\2\u0255\u0256\7o\2\2\u0256\u00a6\3\2\2\2\u0257\u0258\7u\2"+
"\2\u0258\u0259\7w\2\2\u0259\u025a\7d\2\2\u025a\u00a8\3\2\2\2\u025b\u025c"+
"\7/\2\2\u025c\u025d\7@\2\2\u025d\u00aa\3\2\2\2\u025e\u025f\7}\2\2\u025f"+
"\u00ac\3\2\2\2\u0260\u0261\7\177\2\2\u0261\u00ae\3\2\2\2\u0262\u0263\7"+
"c\2\2\u0263\u0264\7u\2\2\u0264\u0265\7o\2\2\u0265\u0266\7u\2\2\u0266\u0267"+
"\7w\2\2\u0267\u0268\7d\2\2\u0268\u00b0\3\2\2\2\u0269\u026a\7u\2\2\u026a"+
"\u026b\7v\2\2\u026b\u026c\7c\2\2\u026c\u026d\7e\2\2\u026d\u026e\7m\2\2"+
"\u026e\u00b2\3\2\2\2\u026f\u0270\7e\2\2\u0270\u0271\7n\2\2\u0271\u0272"+
"\7q\2\2\u0272\u0273\7d\2\2\u0273\u0274\7d\2\2\u0274\u0275\7g\2\2\u0275"+
"\u0276\7t\2\2\u0276\u0277\7u\2\2\u0277\u00b4\3\2\2\2\u0278\u0279\7k\2"+
"\2\u0279\u027a\7h\2\2\u027a\u00b6\3\2\2\2\u027b\u027c\7g\2\2\u027c\u027d"+
"\7n\2\2\u027d\u027e\7u\2\2\u027e\u027f\7g\2\2\u027f\u00b8\3\2\2\2\u0280"+
"\u0281\7k\2\2\u0281\u0282\7h\2\2\u0282\u0283\7a\2\2\u0283\u0284\7e\2\2"+
"\u0284\u0285\7u\2\2\u0285\u00ba\3\2\2\2\u0286\u0287\7k\2\2\u0287\u0288"+
"\7h\2\2\u0288\u0289\7a\2\2\u0289\u028a\7e\2\2\u028a\u028b\7e\2\2\u028b"+
"\u00bc\3\2\2\2\u028c\u028d\7k\2\2\u028d\u028e\7h\2\2\u028e\u028f\7a\2"+
"\2\u028f\u0290\7g\2\2\u0290\u0291\7s\2\2\u0291\u00be\3\2\2\2\u0292\u0293"+
"\7k\2\2\u0293\u0294\7h\2\2\u0294\u0295\7a\2\2\u0295\u0296\7|\2\2\u0296"+
"\u00c0\3\2\2\2\u0297\u0298\7k\2\2\u0298\u0299\7h\2\2\u0299\u029a\7a\2"+
"\2\u029a\u029b\7p\2\2\u029b\u029c\7g\2\2\u029c\u00c2\3\2\2\2\u029d\u029e"+
"\7k\2\2\u029e\u029f\7h\2\2\u029f\u02a0\7a\2\2\u02a0\u02a1\7p\2\2\u02a1"+
"\u02a2\7|\2\2\u02a2\u00c4\3\2\2\2\u02a3\u02a4\7k\2\2\u02a4\u02a5\7h\2"+
"\2\u02a5\u02a6\7a\2\2\u02a6\u02a7\7r\2\2\u02a7\u02a8\7n\2\2\u02a8\u00c6"+
"\3\2\2\2\u02a9\u02aa\7k\2\2\u02aa\u02ab\7h\2\2\u02ab\u02ac\7a\2\2\u02ac"+
"\u02ad\7r\2\2\u02ad\u02ae\7q\2\2\u02ae\u02af\7u\2\2\u02af\u00c8\3\2\2"+
"\2\u02b0\u02b1\7k\2\2\u02b1\u02b2\7h\2\2\u02b2\u02b3\7a\2\2\u02b3\u02b4"+
"\7o\2\2\u02b4\u02b5\7k\2\2\u02b5\u00ca\3\2\2\2\u02b6\u02b7\7k\2\2\u02b7"+
"\u02b8\7h\2\2\u02b8\u02b9\7a\2\2\u02b9\u02ba\7p\2\2\u02ba\u02bb\7g\2\2"+
"\u02bb\u02bc\7i\2\2\u02bc\u00cc\3\2\2\2\u02bd\u02be\7k\2\2\u02be\u02bf"+
"\7h\2\2\u02bf\u02c0\7a\2\2\u02c0\u02c1\7x\2\2\u02c1\u02c2\7u\2\2\u02c2"+
"\u00ce\3\2\2\2\u02c3\u02c4\7k\2\2\u02c4\u02c5\7h\2\2\u02c5\u02c6\7a\2"+
"\2\u02c6\u02c7\7x\2\2\u02c7\u02c8\7e\2\2\u02c8\u00d0\3\2\2\2\u02c9\u02ca"+
"\7h\2\2\u02ca\u02cb\7q\2\2\u02cb\u02cc\7t\2\2\u02cc\u00d2\3\2\2\2\u02cd"+
"\u02ce\7k\2\2\u02ce\u02cf\7p\2\2\u02cf\u00d4\3\2\2\2\u02d0\u02d1\7y\2"+
"\2\u02d1\u02d2\7j\2\2\u02d2\u02d3\7k\2\2\u02d3\u02d4\7n\2\2\u02d4\u02d5"+
"\7g\2\2\u02d5\u00d6\3\2\2\2\u02d6\u02d7\7t\2\2\u02d7\u02d8\7g\2\2\u02d8"+
"\u02d9\7r\2\2\u02d9\u02da\7g\2\2\u02da\u02db\7c\2\2\u02db\u02dc\7v\2\2"+
"\u02dc\u00d8\3\2\2\2\u02dd\u02de\7w\2\2\u02de\u02df\7p\2\2\u02df\u02e0"+
"\7v\2\2\u02e0\u02e1\7k\2\2\u02e1\u02e2\7n\2\2\u02e2\u00da\3\2\2\2\u02e3"+
"\u02e4\7y\2\2\u02e4\u02e5\7j\2\2\u02e5\u02e6\7g\2\2\u02e6\u02e7\7p\2\2"+
"\u02e7\u00dc\3\2\2\2\u02e8\u02ec\t\2\2\2\u02e9\u02eb\t\3\2\2\u02ea\u02e9"+
"\3\2\2\2\u02eb\u02ee\3\2\2\2\u02ec\u02ea\3\2\2\2\u02ec\u02ed\3\2\2\2\u02ed"+
"\u02ef\3\2\2\2\u02ee\u02ec\3\2\2\2\u02ef\u02f0\5\u00dfp\2\u02f0\u02f1"+
"\3\2\2\2\u02f1\u02f2\bo\2\2\u02f2\u00de\3\2\2\2\u02f3\u02f7\7=\2\2\u02f4"+
"\u02f6\n\2\2\2\u02f5\u02f4\3\2\2\2\u02f6\u02f9\3\2\2\2\u02f7\u02f5\3\2"+
"\2\2\u02f7\u02f8\3\2\2\2\u02f8\u02fa\3\2\2\2\u02f9\u02f7\3\2\2\2\u02fa"+
"\u02fb\bp\2\2\u02fb\u00e0\3\2\2\2\u02fc\u02fd\t\3\2\2\u02fd\u02fe\3\2"+
"\2\2\u02fe\u02ff\bq\3\2\u02ff\u00e2\3\2\2\2\u0300\u0302\t\2\2\2\u0301"+
"\u0300\3\2\2\2\u0302\u0303\3\2\2\2\u0303\u0301\3\2\2\2\u0303\u0304\3\2"+
"\2\2\u0304\u00e4\3\2\2\2\u0305\u0309\t\4\2\2\u0306\u0308\t\5\2\2\u0307"+
"\u0306\3\2\2\2\u0308\u030b\3\2\2\2\u0309\u0307\3\2\2\2\u0309\u030a\3\2"+
"\2\2\u030a\u00e6\3\2\2\2\u030b\u0309\3\2\2\2\u030c\u0314\4\62;\2\u030d"+
"\u030f\4\63;\2\u030e\u0310\4\62;\2\u030f\u030e\3\2\2\2\u0310\u0311\3\2"+
"\2\2\u0311\u030f\3\2\2\2\u0311\u0312\3\2\2\2\u0312\u0314\3\2\2\2\u0313"+
"\u030c\3\2\2\2\u0313\u030d\3\2\2\2\u0314\u00e8\3\2\2\2\u0315\u0317\7&"+
"\2\2\u0316\u0318\t\6\2\2\u0317\u0316\3\2\2\2\u0318\u0319\3\2\2\2\u0319"+
"\u0317\3\2\2\2\u0319\u031a\3\2\2\2\u031a\u00ea\3\2\2\2\u031b\u031d\7\'"+
"\2\2\u031c\u031e\4\62\63\2\u031d\u031c\3\2\2\2\u031e\u031f\3\2\2\2\u031f"+
"\u031d\3\2\2\2\u031f\u0320\3\2\2\2\u0320\u00ec\3\2\2\2\u0321\u0322\7("+
"\2\2\u0322\u00ee\3\2\2\2\u0323\u0329\5\u00f1y\2\u0324\u0326\t\7\2\2\u0325"+
"\u0327\t\b\2\2\u0326\u0325\3\2\2\2\u0326\u0327\3\2\2\2\u0327\u0328\3\2"+
"\2\2\u0328\u032a\5\u00f1y\2\u0329\u0324\3\2\2\2\u0329\u032a\3\2\2\2\u032a"+
"\u00f0\3\2\2\2\u032b\u032d\4\62;\2\u032c\u032b\3\2\2\2\u032d\u032e\3\2"+
"\2\2\u032e\u032c\3\2\2\2\u032e\u032f\3\2\2\2\u032f\u0336\3\2\2\2\u0330"+
"\u0332\7\60\2\2\u0331\u0333\4\62;\2\u0332\u0331\3\2\2\2\u0333\u0334\3"+
"\2\2\2\u0334\u0332\3\2\2\2\u0334\u0335\3\2\2\2\u0335\u0337\3\2\2\2\u0336"+
"\u0330\3\2\2\2\u0336\u0337\3\2\2\2\u0337\u00f2\3\2\2\2\u0338\u0339\7^"+
"\2\2\u0339\u033d\13\2\2\2\u033a\u033b\7^\2\2\u033b\u033d\5\u00e3r\2\u033c"+
"\u0338\3\2\2\2\u033c\u033a\3\2\2\2\u033d\u00f4\3\2\2\2\u033e\u0343\7$"+
"\2\2\u033f\u0342\5\u00f3z\2\u0340\u0342\n\t\2\2\u0341\u033f\3\2\2\2\u0341"+
"\u0340\3\2\2\2\u0342\u0345\3\2\2\2\u0343\u0341\3\2\2\2\u0343\u0344\3\2"+
"\2\2\u0344\u0346\3\2\2\2\u0345\u0343\3\2\2\2\u0346\u0347\7$\2\2\u0347"+
"\u0348\b{\4\2\u0348\u00f6\3\2\2\2\u0349\u034a\7}\2\2\u034a\u034b\7}\2"+
"\2\u034b\u034d\3\2\2\2\u034c\u034e\13\2\2\2\u034d\u034c\3\2\2\2\u034e"+
"\u034f\3\2\2\2\u034f\u0350\3\2\2\2\u034f\u034d\3\2\2\2\u0350\u0351\3\2"+
"\2\2\u0351\u0352\7\177\2\2\u0352\u0353\7\177\2\2\u0353\u0354\3\2\2\2\u0354"+
"\u0355\b|\5\2\u0355\u00f8\3\2\2\2\u0356\u0359\7)\2\2\u0357\u035a\5\u00f3"+
"z\2\u0358\u035a\n\t\2\2\u0359\u0357\3\2\2\2\u0359\u0358\3\2\2\2\u035a"+
"\u035b\3\2\2\2\u035b\u035c\7)\2\2\u035c\u035d\b}\6\2\u035d\u00fa\3\2\2"+
"\2\u035e\u035f\7B\2\2\u035f\u0360\7|\2\2\u0360\u0361\7r\2\2\u0361\u00fc"+
"\3\2\2\2\u0362\u0363\7]\2\2\u0363\u0364\7_\2\2\u0364\u00fe\3\2\2\2\26"+
"\2\u02ec\u02f7\u0303\u0309\u0311\u0313\u0317\u0319\u031f\u0326\u0329\u032e"+
"\u0334\u0336\u033c\u0341\u0343\u034f\u0359\7\2\3\2\b\2\2\3{\2\3|\3\3}"+
"\4";
public static final ATN _ATN =
new ATNDeserializer().deserialize(_serializedATN.toCharArray());
static {
_decisionToDFA = new DFA[_ATN.getNumberOfDecisions()];
for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) {
_decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i);
}
}
}
File diff suppressed because it is too large. Load diff
+1 -1
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
File renamed without changes.