mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 20:02:27 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a26b7b248 | ||
|
|
87c28cfdbc | ||
|
|
1f5420010d | ||
|
|
a089c48378 | ||
|
|
3e5deda46c | ||
|
|
7500c6efd0 | ||
|
|
717b5f3b07 | ||
|
|
9f6fa60bf1 | ||
|
|
1e9586f635 | ||
|
|
44f9d5e69e | ||
|
|
7c9b8f7d43 | ||
|
|
845a99d623 | ||
|
|
3d7a4bf81a | ||
|
|
d4b3e35bd2 | ||
|
|
a59f7c75dc | ||
|
|
44fe2369d6 | ||
|
|
aaaab2cfcf | ||
|
|
9a3dab20dc | ||
|
|
20379b5927 | ||
|
|
34dcce67e4 | ||
|
|
0c7f107d01 | ||
|
|
1f89571aa5 | ||
|
|
7eed1ebbf8 | ||
|
|
12cb7d7abe | ||
|
|
c9b16dcbd9 | ||
|
|
dcab6d00bb | ||
|
|
a85743f241 | ||
|
|
14cabde5cf | ||
|
|
cc078503e3 | ||
|
|
2a0c3377f9 | ||
|
|
16454f5560 | ||
|
|
c1343a78f1 | ||
|
|
9d0c65c682 | ||
|
|
9e6408244f |
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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 @@
|
||||
1.9
|
||||
1.11
|
||||
@@ -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"
|
||||
@@ -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 {
|
||||
|
||||
@@ -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("")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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::
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
^^^^
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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!"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ) ;
|
||||
|
||||
@@ -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,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.
Reference in new issue
Block a user