mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1ebc7090d | ||
|
|
054b4636e0 | ||
|
|
e3e7b060b7 | ||
|
|
5ac9c75521 | ||
|
|
07710e0995 | ||
|
|
d6a67f5f2b | ||
|
|
2675623aea | ||
|
|
94263c43d0 | ||
|
|
d8ec03874f | ||
|
|
a7247f5b8b | ||
|
|
4d37581694 | ||
|
|
5d7ddebcad | ||
|
|
53df0eb707 | ||
|
|
8babad9c7c | ||
|
|
8db7aa07bd | ||
|
|
42f4b06ac8 | ||
|
|
f4b50368ba | ||
|
|
db80417bd7 | ||
|
|
7a6f2ecc8c | ||
|
|
f5d556a7f9 | ||
|
|
2aae46d632 | ||
|
|
19ebc6d6b3 | ||
|
|
f88c29e083 | ||
|
|
6ed9899dc7 | ||
|
|
9de7698a5c | ||
|
|
112d2d6058 | ||
|
|
ddb8346711 | ||
|
|
8dd3faf395 | ||
|
|
35f3e8708b | ||
|
|
cfe3fcc9e7 | ||
|
|
66a6659a6e | ||
|
|
88ae3daa42 | ||
|
|
08b8fe01ab | ||
|
|
731132d4b3 | ||
|
|
98acff802f | ||
|
|
5f11f485a2 | ||
|
|
34f3169dda | ||
|
|
a3ef8f814b | ||
|
|
385dd6fc23 | ||
|
|
9af4168ae2 | ||
|
|
a5e0e31b74 | ||
|
|
b385dc8c26 | ||
|
|
92c012b55a | ||
|
|
641f6c05d8 | ||
|
|
788f6b44a6 | ||
|
|
63a4525f06 | ||
|
|
3e34a3ef72 | ||
|
|
0c5e8ca199 | ||
|
|
ff23fb0086 | ||
|
|
56f41d5e34 | ||
|
|
4700a239b9 | ||
|
|
bd5abfb969 | ||
|
|
b93fa75377 | ||
|
|
681ce9c60c | ||
|
|
dd0f0fe415 | ||
|
|
119040fc50 | ||
|
|
551e5688da | ||
|
|
56c1035581 | ||
|
|
ba1e907c79 | ||
|
|
2a3a27c56d | ||
|
|
647af34f5b | ||
|
|
993be6394e | ||
|
|
9a27505315 | ||
|
|
2e37f5dee3 | ||
|
|
03e486c082 | ||
|
|
edc83305a4 | ||
|
|
66e7c51064 | ||
|
|
60244aaf16 | ||
|
|
443391c700 |
No files matched your search
@@ -18,34 +18,24 @@ jobs:
|
||||
git clone --depth=1 https://github.com/irmen/64tass
|
||||
cd 64tass
|
||||
make -j4
|
||||
sudo make install
|
||||
sudo make install
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: adopt
|
||||
distribution: temurin
|
||||
|
||||
- name: Build and test with Gradle
|
||||
run: ./gradlew build shadowJar --no-daemon
|
||||
run: |
|
||||
./gradlew build shadowJar --no-daemon
|
||||
sha256sum -b compiler/build/libs/*-all.jar > compiler/build/libs/hash.txt
|
||||
|
||||
- name: Create compiler shadowJar artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: prog8-compiler-jar-zipped
|
||||
path: compiler/build/libs/*-all.jar
|
||||
path: |
|
||||
compiler/build/libs/*-all.jar
|
||||
compiler/build/libs/hash.txt
|
||||
|
||||
- name: Calculate hash
|
||||
uses: MCJack123/ghaction-generate-release-hashes@v4
|
||||
if: github.event_name == 'release'
|
||||
with:
|
||||
get-assets: true
|
||||
hash-type: sha256
|
||||
file-name: hashes.txt
|
||||
|
||||
- name: Upload hashes
|
||||
uses: actions/upload-artifact@v4
|
||||
if: github.event_name == 'release'
|
||||
with:
|
||||
name: Artifact Hashes
|
||||
path: hashes.txt
|
||||
@@ -52,7 +52,7 @@ What does Prog8 provide?
|
||||
------------------------
|
||||
|
||||
- all advantages of a higher level language over having to write assembly code manually
|
||||
- programs run very fast because compilation to native machine code. It's possible to write games purely in Prog8, and even certain raster interrupt 'demoscene' effects.
|
||||
- programs run very fast because compilation to native machine code
|
||||
- modularity, symbol scoping, subroutines
|
||||
- various data types other than just bytes (16-bit words, floats, strings)
|
||||
- floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do)
|
||||
@@ -71,7 +71,7 @@ What does Prog8 provide?
|
||||
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
||||
- inline assembly allows you to have full control when every cycle or byte matters
|
||||
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16, and provides them also on the C64.
|
||||
- encode strings and characters into petscii or screencodes as desired (C64/Cx16)
|
||||
- encode strings and characters into petscii or screencodes or even other encodings, as desired (C64/Cx16)
|
||||
|
||||
*Rapid edit-compile-run-debug cycle:*
|
||||
|
||||
@@ -119,14 +119,13 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
main {
|
||||
|
||||
ubyte[256] sieve
|
||||
bool[256] sieve
|
||||
ubyte candidate_prime = 2 ; is increased in the loop
|
||||
|
||||
sub start() {
|
||||
sys.memset(sieve, 256, false) ; clear the sieve
|
||||
sys.memset(sieve, 256, 0) ; clear the sieve
|
||||
txt.print("prime numbers up to 255:\n\n")
|
||||
ubyte amount=0
|
||||
repeat {
|
||||
@@ -162,9 +161,6 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
when compiled an ran on a C-64 you'll get:
|
||||
|
||||

|
||||
|
||||
@@ -44,7 +44,7 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
||||
}
|
||||
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
|
||||
is PtIdentifier -> other is PtIdentifier && other.type==type && other.name==name
|
||||
is PtMachineRegister -> other is PtMachineRegister && other.type==type && other.register==register
|
||||
is PtIrRegister -> other is PtIrRegister && other.type==type && other.register==register
|
||||
is PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address
|
||||
is PtNumber -> other is PtNumber && other.type==type && other.number==number
|
||||
is PtBool -> other is PtBool && other.value==value
|
||||
@@ -87,7 +87,7 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
||||
is PtContainmentCheck -> false
|
||||
is PtFunctionCall -> false
|
||||
is PtIdentifier -> true
|
||||
is PtMachineRegister -> true
|
||||
is PtIrRegister -> true
|
||||
is PtMemoryByte -> address is PtNumber || address is PtIdentifier
|
||||
is PtBool -> true
|
||||
is PtNumber -> true
|
||||
@@ -139,13 +139,9 @@ class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
|
||||
|
||||
class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(elementType, position) {
|
||||
val variable: PtIdentifier
|
||||
get() {
|
||||
require((children[0] as? PtIdentifier)?.type in ArrayDatatypes+DataType.STR) // TODO remove
|
||||
return children[0] as PtIdentifier
|
||||
}
|
||||
get() = children[0] as PtIdentifier
|
||||
val index: PtExpression
|
||||
get() = children[1] as PtExpression
|
||||
|
||||
val splitWords: Boolean
|
||||
get() = variable.type in SplitWordArrayTypes
|
||||
|
||||
@@ -210,11 +206,6 @@ class PtFunctionCall(val name: String,
|
||||
val void: Boolean,
|
||||
type: DataType,
|
||||
position: Position) : PtExpression(type, position) {
|
||||
init {
|
||||
if(!void)
|
||||
require(type!=DataType.UNDEFINED)
|
||||
}
|
||||
|
||||
val args: List<PtExpression>
|
||||
get() = children.map { it as PtExpression }
|
||||
}
|
||||
@@ -354,7 +345,7 @@ class PtTypeCast(type: DataType, position: Position) : PtExpression(type, positi
|
||||
|
||||
|
||||
// special node that isn't created from compiling user code, but used internally in the Intermediate Code
|
||||
class PtMachineRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position)
|
||||
class PtIrRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position)
|
||||
|
||||
|
||||
fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else if(expr is PtBool) expr.asInt().toDouble() else null
|
||||
|
||||
@@ -10,7 +10,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
|
||||
fun type(dt: DataType) = "!${dt.name.lowercase()}!"
|
||||
fun txt(node: PtNode): String {
|
||||
return when(node) {
|
||||
is PtAssignTarget -> "<target>"
|
||||
is PtAssignTarget -> if(node.void) "<void>" else "<target>"
|
||||
is PtAssignment -> "<assign>"
|
||||
is PtAugmentedAssign -> "<inplace-assign> ${node.operator}"
|
||||
is PtBreakpoint -> "%breakpoint"
|
||||
@@ -34,7 +34,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
|
||||
str + node.name + "()"
|
||||
}
|
||||
is PtIdentifier -> "${node.name} ${type(node.type)}"
|
||||
is PtMachineRegister -> "VMREG#${node.register} ${type(node.type)}"
|
||||
is PtIrRegister -> "IRREG#${node.register} ${type(node.type)}"
|
||||
is PtMemoryByte -> "@()"
|
||||
is PtNumber -> {
|
||||
val numstr = if(node.type == DataType.FLOAT) node.number.toString() else node.number.toHex()
|
||||
|
||||
@@ -41,9 +41,18 @@ class PtSubroutineParameter(name: String, val type: DataType, position: Position
|
||||
sealed interface IPtAssignment {
|
||||
val children: MutableList<PtNode>
|
||||
val target: PtAssignTarget
|
||||
get() = children[0] as PtAssignTarget
|
||||
get() {
|
||||
if(children.size==2)
|
||||
return children[0] as PtAssignTarget
|
||||
else if(children.size<2)
|
||||
throw AssemblyError("incomplete node")
|
||||
else
|
||||
throw AssemblyError("no singular target")
|
||||
}
|
||||
val value: PtExpression
|
||||
get() = children[1] as PtExpression
|
||||
get() = children.last() as PtExpression
|
||||
val multiTarget: Boolean
|
||||
get() = children.size>2
|
||||
}
|
||||
|
||||
class PtAssignment(position: Position) : PtNode(position), IPtAssignment
|
||||
@@ -51,7 +60,7 @@ class PtAssignment(position: Position) : PtNode(position), IPtAssignment
|
||||
class PtAugmentedAssign(val operator: String, position: Position) : PtNode(position), IPtAssignment
|
||||
|
||||
|
||||
class PtAssignTarget(position: Position) : PtNode(position) {
|
||||
class PtAssignTarget(val void: Boolean, position: Position) : PtNode(position) {
|
||||
val identifier: PtIdentifier?
|
||||
get() = children.single() as? PtIdentifier
|
||||
val array: PtArrayIndexer?
|
||||
@@ -69,7 +78,7 @@ class PtAssignTarget(position: Position) : PtNode(position) {
|
||||
}
|
||||
}
|
||||
|
||||
infix fun isSameAs(expression: PtExpression): Boolean = expression.isSameAs(this)
|
||||
infix fun isSameAs(expression: PtExpression): Boolean = !void && expression.isSameAs(this)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -96,9 +96,9 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"sqrt__ubyte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UBYTE))), DataType.UBYTE),
|
||||
"sqrt__uword" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||
"sqrt__float" to FSignature(true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT),
|
||||
"divmod" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("divident", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("division", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("remainder", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"divmod__ubyte" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UBYTE)), FParam("divident", arrayOf(DataType.UBYTE)), FParam("division", arrayOf(DataType.UBYTE)), FParam("remainder", arrayOf(DataType.UBYTE))), null),
|
||||
"divmod__uword" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UWORD)), FParam("divident", arrayOf(DataType.UWORD)), FParam("division", arrayOf(DataType.UWORD)), FParam("remainder", arrayOf(DataType.UWORD))), null),
|
||||
"divmod" to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("divisor", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("quotient", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("remainder", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"divmod__ubyte" to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UBYTE)), FParam("divisor", arrayOf(DataType.UBYTE)), FParam("quotient", arrayOf(DataType.UBYTE)), FParam("remainder", arrayOf(DataType.UBYTE))), null),
|
||||
"divmod__uword" to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UWORD)), FParam("divisor", arrayOf(DataType.UWORD)), FParam("quotient", arrayOf(DataType.UWORD)), FParam("remainder", arrayOf(DataType.UWORD))), null),
|
||||
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.BOOL),
|
||||
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.BOOL),
|
||||
"lsb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
|
||||
@@ -133,4 +133,9 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"call" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
)
|
||||
|
||||
val InplaceModifyingBuiltinFunctions = setOf("setlsb", "setmsb", "rol", "ror", "rol2", "ror2", "sort", "reverse")
|
||||
val InplaceModifyingBuiltinFunctions = setOf(
|
||||
"setlsb", "setmsb",
|
||||
"rol", "ror", "rol2", "ror2",
|
||||
"sort", "reverse",
|
||||
"divmod", "divmod__ubyte", "divmod__uword"
|
||||
)
|
||||
@@ -20,6 +20,7 @@ class CompilationOptions(val output: OutputType,
|
||||
var asmListfile: Boolean = false,
|
||||
var includeSourcelines: Boolean = false,
|
||||
var dumpVariables: Boolean = false,
|
||||
var dumpSymbols: Boolean = false,
|
||||
var experimentalCodegen: Boolean = false,
|
||||
var varsHighBank: Int? = null,
|
||||
var varsGolden: Boolean = false,
|
||||
|
||||
@@ -26,6 +26,9 @@ interface IMachineDefinition {
|
||||
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
|
||||
fun getFloatAsmBytes(num: Number): String
|
||||
|
||||
fun convertFloatToBytes(num: Double): List<UByte>
|
||||
fun convertBytesToFloat(bytes: List<UByte>): Double
|
||||
|
||||
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
|
||||
fun isIOAddress(address: UInt): Boolean
|
||||
|
||||
@@ -5,7 +5,10 @@ enum class Encoding(val prefix: String) {
|
||||
PETSCII("petscii"), // c64/c128/cx16
|
||||
SCREENCODES("sc"), // c64/c128/cx16
|
||||
ATASCII("atascii"), // atari
|
||||
ISO("iso") // cx16
|
||||
ISO("iso"), // cx16 (iso-8859-15)
|
||||
ISO5("iso5"), // cx16 (iso-8859-5, cyrillic)
|
||||
ISO16("iso16"), // cx16 (iso-8859-16, eastern european)
|
||||
CP437("cp437") // cx16 (ibm pc, codepage 437)
|
||||
}
|
||||
|
||||
interface IStringEncoding {
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package prog8.code.optimize
|
||||
|
||||
import prog8.code.StRomSub
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, errors: IErrorReporter) {
|
||||
fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, st: SymbolTable, errors: IErrorReporter) {
|
||||
if (!options.optimize)
|
||||
return
|
||||
while(errors.noErrors() && optimizeCommonSubExpressions(program, errors)>0) {
|
||||
while (errors.noErrors() &&
|
||||
(optimizeCommonSubExpressions(program, errors)
|
||||
+ optimizeAssignTargets(program, st, errors)) > 0
|
||||
) {
|
||||
// keep rolling
|
||||
}
|
||||
}
|
||||
@@ -27,11 +32,15 @@ private var tempVarCounter = 0
|
||||
private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorReporter): Int {
|
||||
|
||||
fun extractableSubExpr(expr: PtExpression): Boolean {
|
||||
if(expr is PtArrayIndexer && expr.index.isSimple())
|
||||
return false
|
||||
if (expr is PtMemoryByte && expr.address.isSimple())
|
||||
return false
|
||||
|
||||
val result = if(expr is PtBinaryExpression)
|
||||
expr.type !in ByteDatatypes ||
|
||||
!expr.left.isSimple() ||
|
||||
!expr.right.isSimple() ||
|
||||
(expr.operator !in LogicalOperators && expr.operator !in BitwiseOperators)
|
||||
!(expr.left.isSimple() && expr.right.isSimple()) ||
|
||||
(expr.operator !in LogicalOperators && expr.operator !in BitwiseOperators)
|
||||
else if (expr is PtArrayIndexer && expr.type !in ByteDatatypes)
|
||||
true
|
||||
else
|
||||
@@ -89,7 +98,7 @@ private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorRepor
|
||||
singleReplacement2.parent = occurrence2.parent
|
||||
|
||||
val tempassign = PtAssignment(binexpr.position).also { assign ->
|
||||
assign.add(PtAssignTarget(binexpr.position).also { tgt->
|
||||
assign.add(PtAssignTarget(false, binexpr.position).also { tgt->
|
||||
tgt.add(PtIdentifier("$containerScopedName.$tempvarName", datatype, binexpr.position))
|
||||
})
|
||||
assign.add(occurrence1)
|
||||
@@ -109,6 +118,79 @@ private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorRepor
|
||||
}
|
||||
|
||||
|
||||
private fun optimizeAssignTargets(program: PtProgram, st: SymbolTable, errors: IErrorReporter): Int {
|
||||
var changes = 0
|
||||
walkAst(program) { node: PtNode, depth: Int ->
|
||||
if(node is PtAssignment) {
|
||||
val value = node.value
|
||||
val functionName = when(value) {
|
||||
is PtBuiltinFunctionCall -> value.name
|
||||
is PtFunctionCall -> value.name
|
||||
else -> null
|
||||
}
|
||||
if(functionName!=null) {
|
||||
val stNode = st.lookup(functionName)
|
||||
if (stNode is StRomSub) {
|
||||
require(node.children.size==stNode.returns.size+1) {
|
||||
"number of targets must match return values"
|
||||
}
|
||||
node.children.zip(stNode.returns).withIndex().forEach { (index, xx) ->
|
||||
val target = xx.first as PtAssignTarget
|
||||
val returnedRegister = xx.second.register.registerOrPair
|
||||
if(returnedRegister!=null && !target.void && target.identifier!=null) {
|
||||
if(isSame(target.identifier!!, xx.second.type, returnedRegister)) {
|
||||
// output register is already identical to target register, so it can become void
|
||||
val voidTarget = PtAssignTarget(true, target.position)
|
||||
node.children[index] = voidTarget
|
||||
voidTarget.parent = node
|
||||
changes++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(node.children.dropLast(1).all { (it as PtAssignTarget).void }) {
|
||||
// all targets are now void, the whole assignment can be discarded and replaced by just a (void) call to the subroutine
|
||||
val index = node.parent.children.indexOf(node)
|
||||
val voidCall = PtFunctionCall(functionName, true, value.type, value.position)
|
||||
value.children.forEach { voidCall.add(it) }
|
||||
node.parent.children[index] = voidCall
|
||||
voidCall.parent = node.parent
|
||||
changes++
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
return changes
|
||||
}
|
||||
|
||||
internal fun isSame(identifier: PtIdentifier, type: DataType, returnedRegister: RegisterOrPair): Boolean {
|
||||
if(returnedRegister in Cx16VirtualRegisters) {
|
||||
val regname = returnedRegister.name.lowercase()
|
||||
val identifierRegName = identifier.name.substringAfterLast('.')
|
||||
/*
|
||||
cx16.r? UWORD
|
||||
cx16.r?s WORD
|
||||
cx16.r?L UBYTE
|
||||
cx16.r?H UBYTE
|
||||
cx16.r?sL BYTE
|
||||
cx16.r?sH BYTE
|
||||
*/
|
||||
if(identifier.type in ByteDatatypes && type in ByteDatatypes) {
|
||||
if(identifier.name.startsWith("cx16.$regname") && identifierRegName.startsWith(regname)) {
|
||||
return identifierRegName.substring(2) in arrayOf("", "L", "sL") // note: not the -H (msb) variants!
|
||||
}
|
||||
}
|
||||
else if(identifier.type in WordDatatypes && type in WordDatatypes) {
|
||||
if(identifier.name.startsWith("cx16.$regname") && identifierRegName.startsWith(regname)) {
|
||||
return identifierRegName.substring(2) in arrayOf("", "s")
|
||||
}
|
||||
}
|
||||
}
|
||||
return false // there are no identifiers directly corresponding to cpu registers
|
||||
}
|
||||
|
||||
|
||||
internal fun findScopeName(node: PtNode): String {
|
||||
var parent=node
|
||||
while(parent !is PtNamedNode)
|
||||
|
||||
@@ -19,3 +19,15 @@ class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by Cb
|
||||
fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun getCompilationTargetByName(name: String) = when(name.lowercase()) {
|
||||
C64Target.NAME -> C64Target()
|
||||
C128Target.NAME -> C128Target()
|
||||
Cx16Target.NAME -> Cx16Target()
|
||||
PETTarget.NAME -> PETTarget()
|
||||
AtariTarget.NAME -> AtariTarget()
|
||||
VMTarget.NAME -> VMTarget()
|
||||
else -> throw IllegalArgumentException("invalid compilation target")
|
||||
}
|
||||
@@ -4,9 +4,7 @@ import com.github.michaelbull.result.fold
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.IStringEncoding
|
||||
import prog8.code.core.InternalCompilerException
|
||||
import prog8.code.target.cbm.AtasciiEncoding
|
||||
import prog8.code.target.cbm.IsoEncoding
|
||||
import prog8.code.target.cbm.PetsciiEncoding
|
||||
import prog8.code.target.encodings.*
|
||||
|
||||
|
||||
object Encoder: IStringEncoding {
|
||||
@@ -18,6 +16,9 @@ object Encoder: IStringEncoding {
|
||||
Encoding.SCREENCODES -> PetsciiEncoding.encodeScreencode(str, true)
|
||||
Encoding.ISO -> IsoEncoding.encode(str)
|
||||
Encoding.ATASCII -> AtasciiEncoding.encode(str)
|
||||
Encoding.ISO5 -> IsoCyrillicEncoding.encode(str)
|
||||
Encoding.ISO16 -> IsoEasternEncoding.encode(str)
|
||||
Encoding.CP437 -> Cp437Encoding.encode(str)
|
||||
else -> throw InternalCompilerException("unsupported encoding $encoding")
|
||||
}
|
||||
return coded.fold(
|
||||
@@ -31,6 +32,9 @@ object Encoder: IStringEncoding {
|
||||
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)
|
||||
Encoding.ISO -> IsoEncoding.decode(bytes)
|
||||
Encoding.ATASCII -> AtasciiEncoding.decode(bytes)
|
||||
Encoding.ISO5 -> IsoCyrillicEncoding.decode(bytes)
|
||||
Encoding.ISO16 -> IsoEasternEncoding.decode(bytes)
|
||||
Encoding.CP437 -> Cp437Encoding.decode(bytes)
|
||||
else -> throw InternalCompilerException("unsupported encoding $encoding")
|
||||
}
|
||||
return decoded.fold(
|
||||
|
||||
@@ -22,6 +22,8 @@ class AtariMachineDefinition: IMachineDefinition {
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = TODO("atari float asm bytes from number")
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> = TODO("atari float to bytes")
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double = TODO("atari bytes to float")
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.output == OutputType.XEX)
|
||||
|
||||
@@ -25,6 +25,17 @@ class C128MachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> {
|
||||
val m5 = Mflpt5.fromNumber(num)
|
||||
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
|
||||
}
|
||||
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double {
|
||||
require(bytes.size==5) { "need 5 bytes" }
|
||||
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
|
||||
return m5.toDouble()
|
||||
}
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
|
||||
@@ -26,6 +26,17 @@ class C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> {
|
||||
val m5 = Mflpt5.fromNumber(num)
|
||||
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
|
||||
}
|
||||
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double {
|
||||
require(bytes.size==5) { "need 5 bytes" }
|
||||
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
|
||||
return m5.toDouble()
|
||||
}
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
|
||||
@@ -24,6 +24,18 @@ class CX16MachineDefinition: IMachineDefinition {
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> {
|
||||
val m5 = Mflpt5.fromNumber(num)
|
||||
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
|
||||
}
|
||||
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double {
|
||||
require(bytes.size==5) { "need 5 bytes" }
|
||||
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
|
||||
return m5.toDouble()
|
||||
}
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package prog8.code.target.cbm
|
||||
package prog8.code.target.encodings
|
||||
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
@@ -0,0 +1,69 @@
|
||||
package prog8.code.target.encodings
|
||||
|
||||
import com.github.michaelbull.result.Err
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import java.io.CharConversionException
|
||||
import java.nio.charset.Charset
|
||||
|
||||
object Cp437Encoding {
|
||||
val charset: Charset = Charset.forName("IBM437")
|
||||
|
||||
fun encode(str: String): Result<List<UByte>, CharConversionException> {
|
||||
return try {
|
||||
val mapped = str.map { chr ->
|
||||
when (chr) {
|
||||
'\u0000' -> 0u
|
||||
'\u00a0' -> 255u
|
||||
'☺' -> 1u
|
||||
'☻' -> 2u
|
||||
'♥' -> 3u
|
||||
'♦' -> 4u
|
||||
'♣' -> 5u
|
||||
'♠' -> 6u
|
||||
'•' -> 7u
|
||||
'◘' -> 8u
|
||||
'○' -> 9u
|
||||
'◙' -> 10u
|
||||
'♂' -> 11u
|
||||
'♀' -> 12u
|
||||
'♪' -> 13u
|
||||
'♫' -> 14u
|
||||
'☼' -> 15u
|
||||
'►' -> 16u
|
||||
'◄' -> 17u
|
||||
'↕' -> 18u
|
||||
'‼' -> 19u
|
||||
'¶' -> 20u
|
||||
'§' -> 21u
|
||||
'▬' -> 22u
|
||||
'↨' -> 23u
|
||||
'↑' -> 24u
|
||||
'↓' -> 25u
|
||||
'→' -> 26u
|
||||
'←' -> 27u
|
||||
'∟' -> 28u
|
||||
'↔' -> 29u
|
||||
'▲' -> 30u
|
||||
'▼' -> 31u
|
||||
in '\u8000'..'\u80ff' -> {
|
||||
// special case: take the lower 8 bit hex value directly
|
||||
(chr.code - 0x8000).toUByte()
|
||||
}
|
||||
else -> charset.encode(chr.toString())[0].toUByte()
|
||||
}
|
||||
}
|
||||
Ok(mapped)
|
||||
} catch (ce: CharConversionException) {
|
||||
Err(ce)
|
||||
}
|
||||
}
|
||||
|
||||
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
|
||||
return try {
|
||||
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
|
||||
} catch (ce: CharConversionException) {
|
||||
Err(ce)
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-3
@@ -1,4 +1,4 @@
|
||||
package prog8.code.target.cbm
|
||||
package prog8.code.target.encodings
|
||||
|
||||
import com.github.michaelbull.result.Err
|
||||
import com.github.michaelbull.result.Ok
|
||||
@@ -6,8 +6,8 @@ import com.github.michaelbull.result.Result
|
||||
import java.io.CharConversionException
|
||||
import java.nio.charset.Charset
|
||||
|
||||
object IsoEncoding {
|
||||
val charset: Charset = Charset.forName("ISO-8859-15")
|
||||
open class IsoEncodingBase(charsetName: String) {
|
||||
val charset: Charset = Charset.forName(charsetName)
|
||||
|
||||
fun encode(str: String): Result<List<UByte>, CharConversionException> {
|
||||
return try {
|
||||
@@ -35,3 +35,8 @@ object IsoEncoding {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object IsoEncoding: IsoEncodingBase("ISO-8859-15")
|
||||
object IsoCyrillicEncoding: IsoEncodingBase("ISO-8859-5")
|
||||
object IsoEasternEncoding: IsoEncodingBase("ISO-8859-16")
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package prog8.code.target.cbm
|
||||
package prog8.code.target.encodings
|
||||
|
||||
import com.github.michaelbull.result.Err
|
||||
import com.github.michaelbull.result.Ok
|
||||
@@ -25,6 +25,17 @@ class PETMachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> {
|
||||
val m5 = Mflpt5.fromNumber(num)
|
||||
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
|
||||
}
|
||||
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double {
|
||||
require(bytes.size==5) { "need 5 bytes" }
|
||||
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
|
||||
return m5.toDouble()
|
||||
}
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
|
||||
@@ -30,6 +30,26 @@ class VirtualMachineDefinition: IMachineDefinition {
|
||||
return parts.joinToString(", ")
|
||||
}
|
||||
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> {
|
||||
val bits = num.toBits().toULong()
|
||||
val hexStr = bits.toString(16).padStart(16, '0')
|
||||
val parts = hexStr.chunked(2).map { it.toInt(16).toUByte() }
|
||||
return parts
|
||||
}
|
||||
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double {
|
||||
require(bytes.size==8) { "need 8 bytes" }
|
||||
val b0 = bytes[0].toLong() shl (8*7)
|
||||
val b1 = bytes[1].toLong() shl (8*6)
|
||||
val b2 = bytes[2].toLong() shl (8*5)
|
||||
val b3 = bytes[3].toLong() shl (8*4)
|
||||
val b4 = bytes[4].toLong() shl (8*3)
|
||||
val b5 = bytes[5].toLong() shl (8*2)
|
||||
val b6 = bytes[6].toLong() shl (8*1)
|
||||
val b7 = bytes[7].toLong() shl (8*0)
|
||||
return Double.fromBits(b0 or b1 or b2 or b3 or b4 or b5 or b6 or b7)
|
||||
}
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return listOf("syslib")
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
|
||||
if(node.type in SplitWordArrayTypes && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
|
||||
lookupName = lookupName.dropLast(4)
|
||||
}
|
||||
val stNode = st.lookup(lookupName)!!
|
||||
val stNode = st.lookup(lookupName) ?: throw AssemblyError("unknown identifier $node")
|
||||
if(stNode.astNode.definingBlock()?.options?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
@@ -587,7 +587,10 @@ class AsmGen6502Internal (
|
||||
is PtInlineAssembly -> translate(stmt)
|
||||
is PtBuiltinFunctionCall -> builtinFunctionsAsmGen.translateFunctioncallStatement(stmt)
|
||||
is PtFunctionCall -> functioncallAsmGen.translateFunctionCallStatement(stmt)
|
||||
is PtAssignment -> assignmentAsmGen.translate(stmt)
|
||||
is PtAssignment -> {
|
||||
if(stmt.multiTarget) assignmentAsmGen.translateMultiAssign(stmt)
|
||||
else assignmentAsmGen.translate(stmt)
|
||||
}
|
||||
is PtAugmentedAssign -> assignmentAsmGen.translate(stmt)
|
||||
is PtJump -> {
|
||||
val (asmLabel, indirect) = getJumpTarget(stmt)
|
||||
|
||||
@@ -65,8 +65,6 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
// TODO more assembly peephole optimizations
|
||||
|
||||
return numberOfOptimizations
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
}
|
||||
is PtAddressOf -> false
|
||||
is PtIdentifier -> false
|
||||
is PtMachineRegister -> false
|
||||
is PtIrRegister -> false
|
||||
is PtMemoryByte -> return usesOtherRegistersWhileEvaluating(arg.address)
|
||||
is PtNumber -> false
|
||||
is PtBool -> false
|
||||
|
||||
@@ -70,7 +70,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
is PtNumber,
|
||||
is PtBool,
|
||||
is PtIdentifier,
|
||||
is PtMachineRegister,
|
||||
is PtIrRegister,
|
||||
is PtArrayIndexer,
|
||||
is PtPrefix,
|
||||
is PtBinaryExpression -> { /* no cmp necessary the lda has been done just prior */ }
|
||||
@@ -193,7 +193,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
else
|
||||
translateIfElseBodies("beq", stmt)
|
||||
} else {
|
||||
errors.warn("SLOW FALLBACK FOR 'IF' CODEGEN - ask for support", stmt.position) // TODO should have no more of these at all
|
||||
errors.warn("SLOW FALLBACK FOR 'IF' CODEGEN - ask for support", stmt.position) // should not occur ;-)
|
||||
assignConditionValueToRegisterAndTest(stmt.condition)
|
||||
if(jumpAfterIf!=null)
|
||||
translateJumpElseBodies("bne", "beq", jumpAfterIf, stmt.elseScope)
|
||||
|
||||
@@ -635,8 +635,7 @@ internal class ProgramAndVarsGen(
|
||||
} else 0
|
||||
|
||||
when (variable.dt) {
|
||||
DataType.BOOL -> TODO("bool var to asm")
|
||||
DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ${initialValue.toHex()}")
|
||||
DataType.BOOL, DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ${initialValue.toHex()}")
|
||||
DataType.BYTE -> asmgen.out("${variable.name}\t.char $initialValue")
|
||||
DataType.UWORD -> asmgen.out("${variable.name}\t.word ${initialValue.toHex()}")
|
||||
DataType.WORD -> asmgen.out("${variable.name}\t.sint $initialValue")
|
||||
|
||||
@@ -35,7 +35,7 @@ internal class AnyExprAsmGen(
|
||||
require(expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
|
||||
"both operands must be words"
|
||||
}
|
||||
return assignWordBinExpr(expr)
|
||||
throw AssemblyError("expression should have been handled otherwise: word ${expr.operator} at ${expr.position}")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(expr.left.type==DataType.FLOAT && expr.right.type==DataType.FLOAT) {
|
||||
@@ -47,30 +47,6 @@ internal class AnyExprAsmGen(
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignWordBinExpr(expr: PtBinaryExpression): Boolean {
|
||||
when(expr.operator) {
|
||||
"+" -> TODO("word + at ${expr.position}")
|
||||
"-" -> TODO("word - at ${expr.position}")
|
||||
"*" -> TODO("word * at ${expr.position}")
|
||||
"/" -> TODO("word / at ${expr.position}")
|
||||
"<<" -> TODO("word << at ${expr.position}")
|
||||
">>" -> TODO("word >> at ${expr.position}")
|
||||
"%" -> TODO("word % at ${expr.position}")
|
||||
"and" -> TODO("word logical and (with optional shortcircuit) ${expr.position}")
|
||||
"or" -> TODO("word logical or (with optional shortcircuit) ${expr.position}")
|
||||
"&" -> TODO("word and at ${expr.position}")
|
||||
"|" -> TODO("word or at ${expr.position}")
|
||||
"^", "xor" -> TODO("word xor at ${expr.position}")
|
||||
"==" -> TODO("word == at ${expr.position}")
|
||||
"!=" -> TODO("word != at ${expr.position}")
|
||||
"<" -> TODO("word < at ${expr.position}")
|
||||
"<=" -> TODO("word <= at ${expr.position}")
|
||||
">" -> TODO("word > at ${expr.position}")
|
||||
">=" -> TODO("word >= at ${expr.position}")
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignByteBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
|
||||
when(expr.operator) {
|
||||
"+" -> {
|
||||
@@ -89,21 +65,11 @@ internal class AnyExprAsmGen(
|
||||
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||
return true
|
||||
}
|
||||
"*" -> {
|
||||
TODO("byte * at ${expr.position}")
|
||||
}
|
||||
"/" -> {
|
||||
TODO("byte / at ${expr.position}")
|
||||
}
|
||||
"<<" -> {
|
||||
TODO("byte << at ${expr.position}")
|
||||
}
|
||||
">>" -> {
|
||||
TODO("byte >> at ${expr.position}")
|
||||
}
|
||||
"%" -> {
|
||||
TODO("byte % at ${expr.position}")
|
||||
}
|
||||
"*" -> TODO("byte * at ${expr.position}")
|
||||
"/" -> TODO("byte / at ${expr.position}")
|
||||
"<<" -> TODO("byte << at ${expr.position}")
|
||||
">>" -> TODO("byte >> at ${expr.position}")
|
||||
"%" -> TODO("byte % at ${expr.position}")
|
||||
"and" -> TODO("logical and (with optional shortcircuit) ${expr.position}")
|
||||
"or" -> TODO("logical or (with optional shortcircuit) ${expr.position}")
|
||||
"&" -> {
|
||||
@@ -130,24 +96,12 @@ internal class AnyExprAsmGen(
|
||||
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||
return true
|
||||
}
|
||||
"==" -> {
|
||||
TODO("byte == at ${expr.position}")
|
||||
}
|
||||
"!=" -> {
|
||||
TODO("byte != at ${expr.position}")
|
||||
}
|
||||
"<" -> {
|
||||
TODO("byte < at ${expr.position}")
|
||||
}
|
||||
"<=" -> {
|
||||
TODO("byte <= at ${expr.position}")
|
||||
}
|
||||
">" -> {
|
||||
TODO("byte > at ${expr.position}")
|
||||
}
|
||||
">=" -> {
|
||||
TODO("byte >= at ${expr.position}")
|
||||
}
|
||||
"==" -> TODO("byte == at ${expr.position}")
|
||||
"!=" -> TODO("byte != at ${expr.position}")
|
||||
"<" -> TODO("byte < at ${expr.position}")
|
||||
"<=" -> TODO("byte <= at ${expr.position}")
|
||||
">" -> TODO("byte > at ${expr.position}")
|
||||
">=" -> TODO("byte >= at ${expr.position}")
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package prog8.codegen.cpu6502.assignment
|
||||
|
||||
import prog8.code.StMemVar
|
||||
import prog8.code.StRomSub
|
||||
import prog8.code.StRomSubParameter
|
||||
import prog8.code.StStaticVariable
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
@@ -32,6 +34,145 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
augmentableAsmGen.translate(assign, augmentedAssign.definingISub())
|
||||
}
|
||||
|
||||
fun translateMultiAssign(assignment: PtAssignment) {
|
||||
val values = assignment.value as? PtFunctionCall
|
||||
?: throw AssemblyError("only function calls can return multiple values in a multi-assign")
|
||||
|
||||
val sub = asmgen.symbolTable.lookup(values.name) as? StRomSub
|
||||
?: throw AssemblyError("only asmsubs can return multiple values")
|
||||
|
||||
require(sub.returns.size>=2)
|
||||
if(sub.returns.any { it.type==DataType.FLOAT })
|
||||
TODO("deal with (multiple?) FP return registers")
|
||||
|
||||
asmgen.translate(values)
|
||||
|
||||
fun needsToSaveA(registersResults: List<Pair<StRomSubParameter, PtNode>>): Boolean =
|
||||
if(registersResults.isEmpty())
|
||||
false
|
||||
else if(registersResults.all { (it.second as PtAssignTarget).identifier!=null})
|
||||
false
|
||||
else
|
||||
true
|
||||
|
||||
fun assignCarryFlagResult(target: PtAssignTarget, saveA: Boolean) {
|
||||
if(saveA) asmgen.out(" pha")
|
||||
asmgen.out(" lda #0 | rol a")
|
||||
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
|
||||
assignRegisterByte(tgt, CpuRegister.A, false, false)
|
||||
if(saveA) asmgen.out(" pla")
|
||||
}
|
||||
|
||||
fun assignZeroFlagResult(target: PtAssignTarget, saveA: Boolean) {
|
||||
if(saveA) asmgen.out(" pha")
|
||||
asmgen.out("""
|
||||
beq +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
|
||||
assignRegisterByte(tgt, CpuRegister.A, false, false)
|
||||
if(saveA) asmgen.out(" pla")
|
||||
}
|
||||
|
||||
fun assignNegativeFlagResult(target: PtAssignTarget, saveA: Boolean) {
|
||||
if(saveA) asmgen.out(" pha")
|
||||
asmgen.out("""
|
||||
bmi +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
|
||||
assignRegisterByte(tgt, CpuRegister.A, false, false)
|
||||
if(saveA) asmgen.out(" pla")
|
||||
}
|
||||
|
||||
fun assignOverflowFlagResult(target: PtAssignTarget, saveA: Boolean) {
|
||||
if(saveA) asmgen.out(" pha")
|
||||
asmgen.out("""
|
||||
bvs +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
|
||||
assignRegisterByte(tgt, CpuRegister.A, false, false)
|
||||
if(saveA) asmgen.out(" pla")
|
||||
}
|
||||
|
||||
fun assignRegisterResults(registersResults: List<Pair<StRomSubParameter, PtNode>>) {
|
||||
registersResults.forEach { (returns, target) ->
|
||||
target as PtAssignTarget
|
||||
if(!target.void) {
|
||||
val targetIdent = target.identifier
|
||||
val targetMem = target.memory
|
||||
if(targetIdent!=null || targetMem!=null) {
|
||||
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
|
||||
when(returns.type) {
|
||||
in ByteDatatypesWithBoolean -> {
|
||||
if(returns.register.registerOrPair in Cx16VirtualRegisters) {
|
||||
assignVirtualRegister(tgt, returns.register.registerOrPair!!)
|
||||
} else {
|
||||
assignRegisterByte(tgt, returns.register.registerOrPair!!.asCpuRegister(), false, false)
|
||||
}
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
assignRegisterpairWord(tgt, returns.register.registerOrPair!!)
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
else TODO("array target for multi-value assignment") // Not done yet due to result register clobbering complexity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val assignmentTargets = assignment.children.dropLast(1)
|
||||
if(sub.returns.size==assignmentTargets.size) {
|
||||
// because we can only handle integer results right now we can just zip() it all up
|
||||
val (statusFlagResults, registersResults) = sub.returns.zip(assignmentTargets).partition { it.first.register.statusflag!=null }
|
||||
if(statusFlagResults.isNotEmpty()) {
|
||||
if(statusFlagResults.size>1)
|
||||
TODO("handle multiple status flag results")
|
||||
val (returns, target) = statusFlagResults.single()
|
||||
target as PtAssignTarget
|
||||
if(target.void) {
|
||||
// forget about the Carry status flag, only assign the normal return values
|
||||
assignRegisterResults(registersResults)
|
||||
return
|
||||
}
|
||||
if(registersResults.all {
|
||||
val tgt = it.second as PtAssignTarget
|
||||
tgt.void || tgt.identifier!=null})
|
||||
{
|
||||
// all other results are just stored into identifiers directly so first handle those
|
||||
// (simple store instructions that don't modify the carry flag)
|
||||
assignRegisterResults(registersResults)
|
||||
return when(returns.register.statusflag!!) {
|
||||
Statusflag.Pc -> assignCarryFlagResult(target, false)
|
||||
Statusflag.Pz -> assignZeroFlagResult(target, false)
|
||||
Statusflag.Pv -> assignOverflowFlagResult(target, false)
|
||||
Statusflag.Pn -> assignNegativeFlagResult(target, false)
|
||||
}
|
||||
}
|
||||
|
||||
val saveA = needsToSaveA(registersResults)
|
||||
when(returns.register.statusflag!!) {
|
||||
Statusflag.Pc -> assignCarryFlagResult(target, saveA)
|
||||
Statusflag.Pz -> assignZeroFlagResult(target, saveA)
|
||||
Statusflag.Pv -> assignOverflowFlagResult(target, saveA)
|
||||
Statusflag.Pn -> assignNegativeFlagResult(target, saveA)
|
||||
}
|
||||
}
|
||||
assignRegisterResults(registersResults)
|
||||
} else {
|
||||
throw AssemblyError("number of values and targets don't match")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun translateNormalAssignment(assign: AsmAssignment, scope: IPtSubroutine?) {
|
||||
when(assign.source.kind) {
|
||||
SourceStorageKind.LITERALBOOLEAN -> {
|
||||
@@ -597,7 +738,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
assignTrue.add(target)
|
||||
assignTrue.add(PtNumber.fromBoolean(true, assign.position))
|
||||
} else {
|
||||
require(assign.target.datatype in ByteDatatypes)
|
||||
when(assign.target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
if(assign.target.datatype in WordDatatypes) {
|
||||
@@ -618,7 +758,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
val tgt = PtAssignTarget(assign.target.position)
|
||||
val tgt = PtAssignTarget(false, assign.target.position)
|
||||
val targetmem = assign.target.memory!!
|
||||
val mem = PtMemoryByte(targetmem.position)
|
||||
mem.add(targetmem.address)
|
||||
@@ -628,7 +768,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
assignTrue.add(PtNumber.fromBoolean(true, assign.position))
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val tgt = PtAssignTarget(assign.target.position)
|
||||
val tgt = PtAssignTarget(false, assign.target.position)
|
||||
val targetarray = assign.target.array!!
|
||||
val array = PtArrayIndexer(assign.target.datatype, targetarray.position)
|
||||
array.add(targetarray.variable)
|
||||
@@ -667,7 +807,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
internal fun directIntoY(expr: PtExpression): Boolean {
|
||||
return when(expr) {
|
||||
is PtIdentifier -> true
|
||||
is PtMachineRegister -> true
|
||||
is PtIrRegister -> true
|
||||
is PtNumber -> true
|
||||
is PtBuiltinFunctionCall -> expr.name in arrayOf("lsb", "msb")
|
||||
else -> false
|
||||
@@ -1454,6 +1594,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
in ByteDatatypes -> {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.A, dt==DataType.BYTE)
|
||||
asmgen.out("""
|
||||
cmp #0
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1""")
|
||||
@@ -1662,8 +1803,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
|
||||
when (valueDt) {
|
||||
DataType.BOOL -> {
|
||||
if(targetDt in ByteDatatypes) {
|
||||
// optimization to assign boolean expression to byte target (just assign the 0 or 1 directly, no cast needed)
|
||||
if(targetDt in IntegerDatatypes) {
|
||||
// optimization to assign boolean expression to integer target (just assign the 0 or 1 directly)
|
||||
val assignDirect = AsmAssignment(
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen),
|
||||
target,
|
||||
@@ -1672,7 +1813,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
|
||||
)
|
||||
assignExpression(assignDirect, target.scope)
|
||||
} else {
|
||||
throw AssemblyError("expected bool or byte target type")
|
||||
TODO("assign bool to non-integer type $targetDt")
|
||||
}
|
||||
}
|
||||
in ByteDatatypes -> {
|
||||
|
||||
@@ -53,7 +53,7 @@ class TestCodegen: FunSpec({
|
||||
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
|
||||
|
||||
val assign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||
val target = PtAssignTarget(Position.DUMMY).also {
|
||||
val target = PtAssignTarget(false, Position.DUMMY).also {
|
||||
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
|
||||
idx.add(PtIdentifier("main.start.particleX", DataType.ARRAY_UB, Position.DUMMY))
|
||||
idx.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
|
||||
@@ -68,7 +68,7 @@ class TestCodegen: FunSpec({
|
||||
sub.add(assign)
|
||||
|
||||
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
|
||||
val prefixTarget = PtAssignTarget(Position.DUMMY).also {
|
||||
val prefixTarget = PtAssignTarget(false, Position.DUMMY).also {
|
||||
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||
}
|
||||
prefixAssign.add(prefixTarget)
|
||||
@@ -76,7 +76,7 @@ class TestCodegen: FunSpec({
|
||||
sub.add(prefixAssign)
|
||||
|
||||
val numberAssign = PtAugmentedAssign("-=", Position.DUMMY)
|
||||
val numberAssignTarget = PtAssignTarget(Position.DUMMY).also {
|
||||
val numberAssignTarget = PtAssignTarget(false, Position.DUMMY).also {
|
||||
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||
}
|
||||
numberAssign.add(numberAssignTarget)
|
||||
@@ -84,7 +84,7 @@ class TestCodegen: FunSpec({
|
||||
sub.add(numberAssign)
|
||||
|
||||
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||
val cxregAssignTarget = PtAssignTarget(Position.DUMMY).also {
|
||||
val cxregAssignTarget = PtAssignTarget(false, Position.DUMMY).also {
|
||||
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||
}
|
||||
cxregAssign.add(cxregAssignTarget)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.StRomSub
|
||||
import prog8.code.StRomSubParameter
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.intermediate.*
|
||||
@@ -8,16 +10,73 @@ import prog8.intermediate.*
|
||||
internal class AssignmentGen(private val codeGen: IRCodeGen, private val expressionEval: ExpressionGen) {
|
||||
|
||||
internal fun translate(assignment: PtAssignment): IRCodeChunks {
|
||||
if(assignment.target.children.single() is PtMachineRegister)
|
||||
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
|
||||
if(assignment.multiTarget) {
|
||||
val values = assignment.value as? PtFunctionCall
|
||||
?: throw AssemblyError("only function calls can return multiple values in a multi-assign")
|
||||
|
||||
val chunks = translateRegularAssign(assignment)
|
||||
chunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(assignment.position)
|
||||
return chunks
|
||||
val sub = codeGen.symbolTable.lookup(values.name) as? StRomSub
|
||||
?: throw AssemblyError("only asmsubs can return multiple values")
|
||||
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val funcCall = this.expressionEval.translate(values)
|
||||
require(funcCall.multipleResultRegs.size + funcCall.multipleResultFpRegs.size >= 2)
|
||||
if(funcCall.multipleResultFpRegs.isNotEmpty())
|
||||
TODO("deal with (multiple?) FP return registers")
|
||||
val assignmentTargets = assignment.children.dropLast(1)
|
||||
addToResult(result, funcCall, funcCall.resultReg, funcCall.resultFpReg)
|
||||
if(sub.returns.size==assignmentTargets.size) {
|
||||
// Targets and values match. Assign all the things. Skip 'void' targets.
|
||||
sub.returns.zip(assignmentTargets).zip(funcCall.multipleResultRegs).forEach {
|
||||
val target = it.first.second as PtAssignTarget
|
||||
if(!target.void) {
|
||||
val regNumber = it.second
|
||||
val returns = it.first.first
|
||||
result += assignCpuRegister(returns, regNumber, target)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw AssemblyError("number of values and targets don't match")
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
if (assignment.target.children.single() is PtIrRegister)
|
||||
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
|
||||
|
||||
return translateRegularAssign(assignment)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignCpuRegister(returns: StRomSubParameter, regNum: Int, target: PtAssignTarget): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val loadCpuRegInstr = when(returns.register.registerOrPair) {
|
||||
RegisterOrPair.A -> IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=regNum)
|
||||
RegisterOrPair.X -> IRInstruction(Opcode.LOADHX, IRDataType.BYTE, reg1=regNum)
|
||||
RegisterOrPair.Y -> IRInstruction(Opcode.LOADHY, IRDataType.BYTE, reg1=regNum)
|
||||
RegisterOrPair.AX -> IRInstruction(Opcode.LOADHAX, IRDataType.WORD, reg1=regNum)
|
||||
RegisterOrPair.AY -> IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=regNum)
|
||||
RegisterOrPair.XY -> IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=regNum)
|
||||
in Cx16VirtualRegisters -> IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}")
|
||||
null -> {
|
||||
when(returns.register.statusflag) {
|
||||
Statusflag.Pc -> IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=regNum)
|
||||
else -> throw AssemblyError("weird statusflag as returnvalue")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("cannot load register")
|
||||
}
|
||||
addInstr(result, loadCpuRegInstr, null)
|
||||
|
||||
// build an assignment to store the value in the actual target.
|
||||
val assign = PtAssignment(target.position)
|
||||
assign.add(target)
|
||||
assign.add(PtIrRegister(regNum, target.type, target.position))
|
||||
result += translate(assign)
|
||||
return result
|
||||
}
|
||||
|
||||
internal fun translate(augAssign: PtAugmentedAssign): IRCodeChunks {
|
||||
if(augAssign.target.children.single() is PtMachineRegister)
|
||||
// augmented assignment always has just a single target
|
||||
if(augAssign.target.children.single() is PtIrRegister)
|
||||
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
|
||||
|
||||
val target = augAssign.target
|
||||
@@ -270,11 +329,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
else
|
||||
throw AssemblyError("assignment value and target dt mismatch")
|
||||
} else false
|
||||
if (assignment.value is PtMachineRegister) {
|
||||
valueRegister = (assignment.value as PtMachineRegister).register
|
||||
if (assignment.value is PtIrRegister) {
|
||||
valueRegister = (assignment.value as PtIrRegister).register
|
||||
if(extendByteToWord) {
|
||||
valueRegister = codeGen.registers.nextFree()
|
||||
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=valueRegister, reg2=(assignment.value as PtMachineRegister).register), null)
|
||||
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=valueRegister, reg2=(assignment.value as PtIrRegister).register), null)
|
||||
}
|
||||
} else {
|
||||
val tr = expressionEval.translateExpression(assignment.value)
|
||||
|
||||
@@ -854,10 +854,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
|
||||
val assignment = PtAssignment(target.position)
|
||||
val assignTarget = PtAssignTarget(target.position)
|
||||
val assignTarget = PtAssignTarget(false, target.position)
|
||||
assignTarget.children.add(target)
|
||||
assignment.children.add(assignTarget)
|
||||
assignment.children.add(PtMachineRegister(register, target.type, target.position))
|
||||
assignment.children.add(PtIrRegister(register, target.type, target.position))
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
result += codeGen.translateNode(assignment)
|
||||
return result
|
||||
|
||||
@@ -7,8 +7,11 @@ import prog8.code.core.*
|
||||
import prog8.intermediate.*
|
||||
|
||||
|
||||
internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType, val resultReg: Int, val resultFpReg: Int) {
|
||||
constructor(chunks: IRCodeChunk, dt: IRDataType, resultReg: Int, resultFpReg: Int) : this(listOf(chunks), dt, resultReg, resultFpReg)
|
||||
internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType, val resultReg: Int, val resultFpReg: Int,
|
||||
val multipleResultRegs: List<Int> = emptyList(), val multipleResultFpRegs: List<Int> = emptyList()
|
||||
) {
|
||||
constructor(chunk: IRCodeChunk, dt: IRDataType, resultReg: Int, resultFpReg: Int, multipleResultRegs: List<Int> = emptyList(), multipleResultFpRegs: List<Int> = emptyList())
|
||||
: this(listOf(chunk), dt, resultReg, resultFpReg, multipleResultRegs, multipleResultFpRegs)
|
||||
|
||||
companion object {
|
||||
val EMPTY: ExpressionCodeResult = ExpressionCodeResult(emptyList(), IRDataType.BYTE, -1, -1)
|
||||
@@ -24,7 +27,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
|
||||
fun translateExpression(expr: PtExpression): ExpressionCodeResult {
|
||||
return when (expr) {
|
||||
is PtMachineRegister -> {
|
||||
is PtIrRegister -> {
|
||||
ExpressionCodeResult(emptyList(), irType(expr.type), expr.register, -1)
|
||||
}
|
||||
is PtBool -> {
|
||||
@@ -463,7 +466,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, null)))
|
||||
result += tr.chunks
|
||||
}
|
||||
// return value
|
||||
// return value (always singular for normal Subs)
|
||||
val returnRegSpec = if(fcall.void) null else {
|
||||
val returnIrType = irType(callTarget.returnType!!)
|
||||
if(returnIrType==IRDataType.FLOAT)
|
||||
@@ -472,7 +475,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), null)
|
||||
}
|
||||
// create the call
|
||||
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec)), null)
|
||||
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name,
|
||||
fcallArgs = FunctionCallArgs(argRegisters, if(returnRegSpec==null) emptyList() else listOf(returnRegSpec))), null)
|
||||
return if(fcall.void)
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
else if(fcall.type==DataType.FLOAT)
|
||||
@@ -493,44 +497,57 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
else
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, parameter.register)))
|
||||
result += tr.chunks
|
||||
when(parameter.register.registerOrPair) {
|
||||
RegisterOrPair.A -> addInstr(result, IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1=tr.resultReg), null)
|
||||
RegisterOrPair.X -> addInstr(result, IRInstruction(Opcode.STOREHX, IRDataType.BYTE, reg1=tr.resultReg), null)
|
||||
RegisterOrPair.Y -> addInstr(result, IRInstruction(Opcode.STOREHY, IRDataType.BYTE, reg1=tr.resultReg), null)
|
||||
RegisterOrPair.AX -> addInstr(result, IRInstruction(Opcode.STOREHAX, IRDataType.WORD, reg1=tr.resultReg), null)
|
||||
RegisterOrPair.AY -> addInstr(result, IRInstruction(Opcode.STOREHAY, IRDataType.WORD, reg1=tr.resultReg), null)
|
||||
RegisterOrPair.XY -> addInstr(result, IRInstruction(Opcode.STOREHXY, IRDataType.WORD, reg1=tr.resultReg), null)
|
||||
in Cx16VirtualRegisters -> {
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, paramDt, reg1=tr.resultReg, labelSymbol = "cx16.${parameter.register.registerOrPair.toString().lowercase()}"), null)
|
||||
}
|
||||
null -> when(parameter.register.statusflag) {
|
||||
// TODO: do the statusflag argument as last
|
||||
Statusflag.Pc -> addInstr(result, IRInstruction(Opcode.LSR, paramDt, reg1=tr.resultReg), null)
|
||||
else -> throw AssemblyError("weird statusflag as param")
|
||||
}
|
||||
else -> throw AssemblyError("unsupported register arg")
|
||||
}
|
||||
}
|
||||
// return value
|
||||
var statusFlagResult: Statusflag? = null
|
||||
|
||||
if(callTarget.returns.size>1)
|
||||
return callRomSubWithMultipleReturnValues(callTarget, fcall, argRegisters, result)
|
||||
|
||||
// return a single value (or nothing)
|
||||
val returnRegSpec = if(fcall.void) null else {
|
||||
if(callTarget.returns.isEmpty())
|
||||
null
|
||||
else if(callTarget.returns.size==1) {
|
||||
else {
|
||||
val returns = callTarget.returns[0]
|
||||
val returnIrType = irType(returns.type)
|
||||
if(returnIrType==IRDataType.FLOAT)
|
||||
if (returnIrType == IRDataType.FLOAT)
|
||||
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFreeFloat(), returns.register)
|
||||
else {
|
||||
statusFlagResult = returns.register.statusflag
|
||||
val returnRegister = if(statusFlagResult==null) codeGen.registers.nextFree() else -1
|
||||
val returnRegister = codeGen.registers.nextFree()
|
||||
FunctionCallArgs.RegSpec(returnIrType, returnRegister, returns.register)
|
||||
}
|
||||
} else {
|
||||
// multiple return values: take the first *register* (not status flag) return value and ignore the rest.
|
||||
val returns = callTarget.returns.first { it.register.registerOrPair!=null }
|
||||
val returnIrType = irType(returns.type)
|
||||
if(returnIrType==IRDataType.FLOAT)
|
||||
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFreeFloat(), returns.register)
|
||||
else
|
||||
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), returns.register)
|
||||
}
|
||||
}
|
||||
// create the call
|
||||
val returnRegs = if(returnRegSpec==null) emptyList() else listOf(returnRegSpec)
|
||||
val call =
|
||||
if(callTarget.address==null)
|
||||
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec))
|
||||
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
|
||||
else
|
||||
IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec))
|
||||
IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
|
||||
addInstr(result, call, null)
|
||||
var finalReturnRegister = returnRegSpec?.registerNum ?: -1
|
||||
|
||||
if(fcall.parent is PtAssignment || fcall.parent is PtTypeCast) {
|
||||
// look if the status flag bit should actually be returned as a 0/1 byte value in a result register (so it can be assigned)
|
||||
if(statusFlagResult!=null && returnRegSpec!=null) {
|
||||
val statusFlagResult = returnRegSpec?.cpuRegister?.statusflag
|
||||
if(statusFlagResult!=null) {
|
||||
// assign status flag bit to the return value register
|
||||
finalReturnRegister = returnRegSpec.registerNum
|
||||
if(finalReturnRegister<0)
|
||||
@@ -573,6 +590,29 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun callRomSubWithMultipleReturnValues(
|
||||
callTarget: StRomSub,
|
||||
fcall: PtFunctionCall,
|
||||
argRegisters: MutableList<FunctionCallArgs.ArgumentSpec>,
|
||||
result: MutableList<IRCodeChunkBase>
|
||||
): ExpressionCodeResult {
|
||||
// return multiple values
|
||||
val returnRegisters = callTarget.returns.map {
|
||||
val regnum = if(it.type==DataType.FLOAT) codeGen.registers.nextFreeFloat() else codeGen.registers.nextFree()
|
||||
FunctionCallArgs.RegSpec(irType(it.type), regnum, it.register)
|
||||
}
|
||||
// create the call
|
||||
val call =
|
||||
if(callTarget.address==null)
|
||||
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegisters))
|
||||
else
|
||||
IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegisters))
|
||||
addInstr(result, call, null)
|
||||
val resultRegs = returnRegisters.filter{it.dt!=IRDataType.FLOAT}.map{it.registerNum}
|
||||
val resultFpRegs = returnRegisters.filter{it.dt==IRDataType.FLOAT}.map{it.registerNum}
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1, resultRegs, resultFpRegs)
|
||||
}
|
||||
|
||||
private fun operatorGreaterThan(
|
||||
binExpr: PtBinaryExpression,
|
||||
vmDt: IRDataType,
|
||||
|
||||
@@ -1451,6 +1451,10 @@ class IRCodeGen(
|
||||
}
|
||||
|
||||
when(val cond=ifElse.condition) {
|
||||
is PtBool -> {
|
||||
// normally this will be optimized away, but not with -noopt
|
||||
translateSimple(cond, Opcode.BSTEQ)
|
||||
}
|
||||
is PtTypeCast -> {
|
||||
require(cond.type==DataType.BOOL && cond.value.type in NumericDatatypes)
|
||||
translateSimple(cond, Opcode.BSTEQ)
|
||||
@@ -1650,7 +1654,8 @@ class IRCodeGen(
|
||||
val args = params.map { (dt, reg)->
|
||||
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(dt, reg, null))
|
||||
}
|
||||
val returnSpec = if(returns==null) null else FunctionCallArgs.RegSpec(returns.first, returns.second, null)
|
||||
// for now, syscalls have 0 or 1 return value
|
||||
val returnSpec = if(returns==null) emptyList() else listOf(FunctionCallArgs.RegSpec(returns.first, returns.second, null))
|
||||
it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number, fcallArgs = FunctionCallArgs(args, returnSpec))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,15 +52,12 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
|| removeWeirdBranches(chunk1, chunk2, indexedInstructions)
|
||||
|| removeDoubleSecClc(chunk1, indexedInstructions)
|
||||
|| cleanupPushPop(chunk1, indexedInstructions)
|
||||
// TODO other optimizations
|
||||
} while (changed)
|
||||
}
|
||||
}
|
||||
removeEmptyChunks(sub)
|
||||
}
|
||||
|
||||
// TODO also do register optimization step here at the end?
|
||||
|
||||
irprog.linkChunks() // re-link
|
||||
}
|
||||
|
||||
@@ -433,21 +430,34 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
||||
}
|
||||
|
||||
private fun removeDoubleLoadsAndStores(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
return false
|
||||
|
||||
/*
|
||||
var changed = false
|
||||
indexedInstructions.forEach { (idx, ins) ->
|
||||
if(ins.opcode==Opcode.STOREM) {
|
||||
val prev = indexedInstructions[idx-1].value
|
||||
if(prev.opcode==Opcode.LOADM) {
|
||||
// loadm.X rX,something | storem.X rX,something ?? -> get rid of the store.
|
||||
if(ins.labelSymbol!=null && ins.labelSymbol==prev.labelSymbol && ins.labelSymbolOffset==prev.labelSymbolOffset) {
|
||||
changed=true
|
||||
chunk.instructions.removeAt(idx)
|
||||
}
|
||||
else if(ins.address!=null && ins.address==prev.address) {
|
||||
changed=true
|
||||
chunk.instructions.removeAt(idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: detect multiple loads to the same target registers, only keep first (if source is not I/O memory)
|
||||
// TODO: detect multiple stores to the same target, only keep first (if target is not I/O memory)
|
||||
// TODO: detect multiple float ffrom/fto to the same target, only keep first
|
||||
// TODO: detect subsequent same xors/nots/negs, remove the pairs completely as they cancel out
|
||||
// TODO: detect multiple same ands, ors; only keep first
|
||||
// TODO: (hard) detect multiple registers being assigned the same value (and not changed) - use only 1 of them
|
||||
/*
|
||||
Possible other optimizations:
|
||||
// detect multiple loads to the same target registers, only keep first (if source is not I/O memory)
|
||||
// detect multiple stores to the same target, only keep first (if target is not I/O memory)
|
||||
// detect multiple float ffrom/fto to the same target, only keep first
|
||||
// detect subsequent same xors/nots/negs, remove the pairs completely as they cancel out
|
||||
// detect multiple same ands, ors; only keep first
|
||||
// detect multiple registers being assigned the same value (and not changed) - use only 1 of them (hard!)
|
||||
// ...
|
||||
*/
|
||||
}
|
||||
return changed
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ class TestVmCodeGen: FunSpec({
|
||||
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
|
||||
|
||||
val assign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||
val target = PtAssignTarget(Position.DUMMY).also {
|
||||
val target = PtAssignTarget(false, Position.DUMMY).also {
|
||||
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
|
||||
idx.add(PtIdentifier("main.start.particleX", DataType.ARRAY_UB, Position.DUMMY))
|
||||
idx.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
|
||||
@@ -65,7 +65,7 @@ class TestVmCodeGen: FunSpec({
|
||||
sub.add(assign)
|
||||
|
||||
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
|
||||
val prefixTarget = PtAssignTarget(Position.DUMMY).also {
|
||||
val prefixTarget = PtAssignTarget(false, Position.DUMMY).also {
|
||||
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||
}
|
||||
prefixAssign.add(prefixTarget)
|
||||
@@ -73,7 +73,7 @@ class TestVmCodeGen: FunSpec({
|
||||
sub.add(prefixAssign)
|
||||
|
||||
val numberAssign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||
val numberAssignTarget = PtAssignTarget(Position.DUMMY).also {
|
||||
val numberAssignTarget = PtAssignTarget(false, Position.DUMMY).also {
|
||||
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||
}
|
||||
numberAssign.add(numberAssignTarget)
|
||||
@@ -81,7 +81,7 @@ class TestVmCodeGen: FunSpec({
|
||||
sub.add(numberAssign)
|
||||
|
||||
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||
val cxregAssignTarget = PtAssignTarget(Position.DUMMY).also {
|
||||
val cxregAssignTarget = PtAssignTarget(false, Position.DUMMY).also {
|
||||
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||
}
|
||||
cxregAssign.add(cxregAssignTarget)
|
||||
|
||||
@@ -435,7 +435,6 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
subrightIsConst: Boolean): IAstModification?
|
||||
{
|
||||
// NOTE: THESE REORDERINGS ARE ONLY VALID FOR FLOATING POINT CONSTANTS
|
||||
// TODO: this implements only a small set of possible reorderings at this time, we could perhaps add more
|
||||
|
||||
if(expr.operator==subExpr.operator) {
|
||||
// both operators are the same.
|
||||
|
||||
@@ -75,10 +75,11 @@ class VarConstantValueTypeAdjuster(
|
||||
val declValue = decl.value?.constValue(program)
|
||||
if (declValue != null) {
|
||||
// variable is never written to, so it can be replaced with a constant, IF the value is a constant
|
||||
errors.info("variable is never written to and was replaced by a constant", decl.position)
|
||||
errors.info("variable '${decl.name}' is never written to and was replaced by a constant", decl.position)
|
||||
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, declValue, decl.sharedWithAsm, decl.splitArray, decl.position)
|
||||
decl.value = null
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(decl, const, parent),
|
||||
IAstModification.ReplaceNode(decl, const, parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -94,7 +95,7 @@ class VarConstantValueTypeAdjuster(
|
||||
)
|
||||
}
|
||||
// variable only has a single write and it is the initialization value, so it can be replaced with a constant, IF the value is a constant
|
||||
errors.info("variable is never written to and was replaced by a constant", decl.position)
|
||||
errors.info("variable '${decl.name}' is never written to and was replaced by a constant", decl.position)
|
||||
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, singleAssignment.value, decl.sharedWithAsm, decl.splitArray, decl.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(decl, const, parent),
|
||||
@@ -103,15 +104,15 @@ class VarConstantValueTypeAdjuster(
|
||||
}
|
||||
}
|
||||
/*
|
||||
TODO: need to check if there are no variable usages between the declaration and the assignment (because these rely on the original initialization value)
|
||||
if(writes.size==2) {
|
||||
val firstAssignment = writes[0].parent as? Assignment
|
||||
val secondAssignment = writes[1].parent as? Assignment
|
||||
if(firstAssignment?.origin==AssignmentOrigin.VARINIT && secondAssignment?.value?.constValue(program)!=null) {
|
||||
errors.warn("variable is only assigned once here, consider using this as the initialization value in the declaration instead", secondAssignment.position)
|
||||
TODO: need to check if there are no variable usages between the declaration and the assignment (because these rely on the original initialization value)
|
||||
if(writes.size==2) {
|
||||
val firstAssignment = writes[0].parent as? Assignment
|
||||
val secondAssignment = writes[1].parent as? Assignment
|
||||
if(firstAssignment?.origin==AssignmentOrigin.VARINIT && secondAssignment?.value?.constValue(program)!=null) {
|
||||
errors.warn("variable is only assigned once here, consider using this as the initialization value in the declaration instead", secondAssignment.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
*/
|
||||
}
|
||||
|
||||
return noModifications
|
||||
@@ -309,7 +310,7 @@ internal class ConstantIdentifierReplacer(
|
||||
val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
|
||||
return if(arrayIdx.parent is AssignTarget) {
|
||||
val memwrite = DirectMemoryWrite(add, identifier.position)
|
||||
val assignTarget = AssignTarget(null, null, memwrite, identifier.position)
|
||||
val assignTarget = AssignTarget(null, null, memwrite, null, false, identifier.position)
|
||||
listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
|
||||
} else {
|
||||
val memread = DirectMemoryRead(add, identifier.position)
|
||||
|
||||
@@ -13,8 +13,6 @@ import kotlin.math.abs
|
||||
import kotlin.math.log2
|
||||
import kotlin.math.pow
|
||||
|
||||
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has?
|
||||
|
||||
class ExpressionSimplifier(private val program: Program, private val options: CompilationOptions, private val errors: IErrorReporter) : AstWalker() {
|
||||
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
||||
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
||||
@@ -242,10 +240,13 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
|
||||
// optimize boolean constant comparisons
|
||||
if(expr.operator=="==") {
|
||||
if(rightDt==DataType.BOOL && leftDt==DataType.BOOL) {
|
||||
if(rightVal?.asBooleanValue==true)
|
||||
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||
else
|
||||
return listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
|
||||
val rightConstBool = rightVal?.asBooleanValue
|
||||
if(rightConstBool!=null) {
|
||||
return if(rightConstBool)
|
||||
listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||
else
|
||||
listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
|
||||
}
|
||||
}
|
||||
if (rightVal?.number == 1.0) {
|
||||
if (options.strictBool) {
|
||||
@@ -740,7 +741,7 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
|
||||
in powersOfTwo -> {
|
||||
if (leftDt==DataType.UBYTE || leftDt==DataType.UWORD) {
|
||||
// Unsigned number divided by a power of two => shift right
|
||||
// Signed number can't simply be bitshifted in this case (due to rounding issues for negative values), TODO is this correct???
|
||||
// Signed number can't simply be bitshifted in this case (due to rounding issues for negative values),
|
||||
// so we leave that as is and let the code generator deal with it.
|
||||
val numshifts = log2(cv).toInt()
|
||||
return BinaryExpression(expr.left, ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
|
||||
|
||||
@@ -162,7 +162,7 @@ class StatementOptimizer(private val program: Program,
|
||||
// for loop over a (constant) range of just a single value-- optimize the loop away
|
||||
// loopvar/reg = range value , follow by block
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
}
|
||||
@@ -177,7 +177,7 @@ class StatementOptimizer(private val program: Program,
|
||||
val character = options.compTarget.encodeString(sv.value, sv.encoding)[0]
|
||||
val byte = NumericLiteral(DataType.UBYTE, character.toDouble(), iterable.position)
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
}
|
||||
@@ -190,7 +190,7 @@ class StatementOptimizer(private val program: Program,
|
||||
if(av!=null) {
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(
|
||||
AssignTarget(forLoop.loopVar, null, null, forLoop.position), NumericLiteral.optimalInteger(av.toInt(), iterable.position),
|
||||
AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), NumericLiteral.optimalInteger(av.toInt(), iterable.position),
|
||||
AssignmentOrigin.OPTIMIZER, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
@@ -206,7 +206,7 @@ class StatementOptimizer(private val program: Program,
|
||||
val pos = forLoop.position
|
||||
val loopVar = forLoop.loopVar
|
||||
val addSubOne = BinaryExpression(loopVar.copy(), if(inc) "+" else "-", NumericLiteral.optimalInteger(1, pos), pos, false)
|
||||
return Assignment(AssignTarget(loopVar.copy(), null, null, pos), addSubOne, AssignmentOrigin.USERCODE, pos)
|
||||
return Assignment(AssignTarget(loopVar.copy(), null, null, null, false, pos), addSubOne, AssignmentOrigin.USERCODE, pos)
|
||||
}
|
||||
|
||||
if (range != null && range.to.constValue(program)?.number == 0.0 && range.step.constValue(program)?.number==-1.0) {
|
||||
@@ -219,7 +219,7 @@ class StatementOptimizer(private val program: Program,
|
||||
val decOne = incOrDec(false)
|
||||
forLoop.body.statements.add(decOne)
|
||||
val replacement = AnonymousScope(mutableListOf(
|
||||
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
|
||||
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, null, false, pos),
|
||||
fromExpr, AssignmentOrigin.OPTIMIZER, pos),
|
||||
UntilLoop(forLoop.body, condition, pos)
|
||||
), pos)
|
||||
@@ -454,12 +454,6 @@ class StatementOptimizer(private val program: Program,
|
||||
|
||||
override fun after(whenStmt: When, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
fun replaceWithIf(condition: Expression, trueBlock: AnonymousScope, elseBlock: AnonymousScope?): List<IAstModification> {
|
||||
val ifStmt = IfElse(condition, trueBlock, elseBlock ?: AnonymousScope(mutableListOf(), whenStmt.position), whenStmt.position)
|
||||
errors.info("for boolean condition a normal if statement is preferred", whenStmt.position)
|
||||
return listOf(IAstModification.ReplaceNode(whenStmt, ifStmt, parent))
|
||||
}
|
||||
|
||||
val constantValue = whenStmt.condition.constValue(program)?.number
|
||||
if(constantValue!=null) {
|
||||
// when condition is a constant
|
||||
|
||||
@@ -69,14 +69,14 @@ class UnusedCodeRemover(private val program: Program,
|
||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
if("force_output" !in block.options()) {
|
||||
if (block.containsNoCodeNorVars) {
|
||||
if(block.name != internedStringsModuleName) {
|
||||
if(block.name != internedStringsModuleName && "ignore_unused" !in block.options()) {
|
||||
if(!block.statements.any { it is Subroutine && it.hasBeenInlined })
|
||||
errors.info("removing unused block '${block.name}'", block.position)
|
||||
}
|
||||
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
|
||||
}
|
||||
if(callgraph.unused(block)) {
|
||||
if(block.statements.any{ it !is VarDecl || it.type== VarDeclType.VAR}) {
|
||||
if(block.statements.any{ it !is VarDecl || it.type== VarDeclType.VAR} && "ignore_unused" !in block.options()) {
|
||||
if(!block.statements.any { it is Subroutine && it.hasBeenInlined })
|
||||
errors.info("removing unused block '${block.name}'", block.position)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
%import syslib
|
||||
%import conv
|
||||
%import shared_textio_functions
|
||||
|
||||
txt {
|
||||
|
||||
@@ -17,6 +16,10 @@ sub clear_screen() {
|
||||
chrout(125)
|
||||
}
|
||||
|
||||
sub cls() {
|
||||
chrout(125)
|
||||
}
|
||||
|
||||
sub nl() {
|
||||
chrout('\n')
|
||||
}
|
||||
@@ -148,15 +151,15 @@ _tmp_outchar
|
||||
}
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string from A/Y
|
||||
; ---- print zero terminated string from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to CHROUT of that single char.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
sty P8ZP_SCRATCH_REG
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_B1),y
|
||||
- lda (P8ZP_SCRATCH_W2),y
|
||||
beq +
|
||||
jsr chrout
|
||||
iny
|
||||
@@ -168,13 +171,13 @@ asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
jsr conv.ubyte2decimal
|
||||
jsr conv.internal_ubyte2decimal
|
||||
pha
|
||||
tya
|
||||
jsr chrout
|
||||
pla
|
||||
jsr chrout
|
||||
txa
|
||||
jsr chrout
|
||||
pla
|
||||
jmp chrout
|
||||
}}
|
||||
}
|
||||
@@ -182,21 +185,21 @@ asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
|
||||
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
jsr conv.ubyte2decimal
|
||||
jsr conv.internal_ubyte2decimal
|
||||
_print_byte_digits
|
||||
pha
|
||||
cpy #'0'
|
||||
beq +
|
||||
tya
|
||||
jsr chrout
|
||||
pla
|
||||
txa
|
||||
jsr chrout
|
||||
jmp _ones
|
||||
+ pla
|
||||
cmp #'0'
|
||||
+ cpx #'0'
|
||||
beq _ones
|
||||
txa
|
||||
jsr chrout
|
||||
_ones txa
|
||||
_ones pla
|
||||
jmp chrout
|
||||
}}
|
||||
}
|
||||
@@ -210,7 +213,7 @@ asmsub print_b (byte value @ A) clobbers(A,X,Y) {
|
||||
lda #'-'
|
||||
jsr chrout
|
||||
+ pla
|
||||
jsr conv.byte2decimal
|
||||
jsr conv.internal_byte2decimal
|
||||
jmp print_ub._print_byte_digits
|
||||
}}
|
||||
}
|
||||
@@ -223,7 +226,7 @@ asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
lda #'$'
|
||||
jsr chrout
|
||||
pla
|
||||
+ jsr conv.ubyte2hex
|
||||
+ jsr conv.internal_ubyte2hex
|
||||
jsr chrout
|
||||
tya
|
||||
jmp chrout
|
||||
@@ -277,9 +280,9 @@ asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
jsr conv.uword2decimal
|
||||
jsr conv.internal_uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
- lda conv.internal_uword2decimal.decTenThousands,y
|
||||
beq +
|
||||
jsr chrout
|
||||
iny
|
||||
@@ -291,9 +294,9 @@ asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
|
||||
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
jsr conv.uword2decimal
|
||||
jsr conv.internal_uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
- lda conv.internal_uword2decimal.decTenThousands,y
|
||||
beq _allzero
|
||||
cmp #'0'
|
||||
bne _gotdigit
|
||||
@@ -303,7 +306,7 @@ asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
|
||||
_gotdigit
|
||||
jsr chrout
|
||||
iny
|
||||
lda conv.uword2decimal.decTenThousands,y
|
||||
lda conv.internal_uword2decimal.decTenThousands,y
|
||||
bne _gotdigit
|
||||
rts
|
||||
_allzero
|
||||
|
||||
@@ -93,11 +93,12 @@ romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get current window dimensions into X (columns) and Y (rows) NOTE: changed behavior compared to VIC/C64/PET SCREEN() routine!
|
||||
romsub $FFED = SCRORG() -> ubyte @ X, ubyte @ Y ; get current window dimensions into X (columns) and Y (rows) NOTE: changed behavior compared to VIC/C64/PET SCREEN() routine!
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
@@ -105,15 +106,20 @@ romsub $FFF3 = IOBASE() -> uword @ XY ; read base addr
|
||||
|
||||
; ---- utilities -----
|
||||
|
||||
asmsub STOP2() clobbers(X) -> bool @A {
|
||||
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
|
||||
inline asmsub STOP2() clobbers(X,A) -> bool @Pz {
|
||||
; -- just like STOP, but omits the special keys result value in A.
|
||||
; just for convenience because most of the times you're only interested in the stop pressed or not status.
|
||||
%asm {{
|
||||
jsr cbm.STOP
|
||||
beq +
|
||||
lda #0
|
||||
rts
|
||||
+ lda #1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub GETIN2() clobbers(X,Y) -> ubyte @A {
|
||||
; -- just like GETIN, but omits the carry flag result value.
|
||||
; just for convenience because GETIN is so often used to just read keyboard input,
|
||||
; where you don't have to deal with a potential error status
|
||||
%asm {{
|
||||
jsr cbm.GETIN
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
%import syslib
|
||||
%import conv
|
||||
%import shared_textio_functions
|
||||
%import shared_cbm_textio_functions
|
||||
|
||||
|
||||
txt {
|
||||
@@ -13,11 +13,17 @@ txt {
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
|
||||
sub clear_screen() {
|
||||
chrout(147)
|
||||
}
|
||||
|
||||
sub cls() {
|
||||
chrout(147)
|
||||
}
|
||||
|
||||
sub home() {
|
||||
chrout(19)
|
||||
}
|
||||
@@ -288,216 +294,6 @@ _scroll_screen ; scroll only the screen memory
|
||||
}}
|
||||
}
|
||||
|
||||
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string, in PETSCII encoding, from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to cbm.CHROUT of that single char.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
sty P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_B1),y
|
||||
beq +
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
jsr conv.ubyte2decimal
|
||||
pha
|
||||
tya
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
jsr cbm.CHROUT
|
||||
txa
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
jsr conv.ubyte2decimal
|
||||
_print_byte_digits
|
||||
pha
|
||||
cpy #'0'
|
||||
beq +
|
||||
tya
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
jsr cbm.CHROUT
|
||||
jmp _ones
|
||||
+ pla
|
||||
cmp #'0'
|
||||
beq _ones
|
||||
jsr cbm.CHROUT
|
||||
_ones txa
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the byte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
pha
|
||||
cmp #0
|
||||
bpl +
|
||||
lda #'-'
|
||||
jsr cbm.CHROUT
|
||||
+ pla
|
||||
jsr conv.byte2decimal
|
||||
jmp print_ub._print_byte_digits
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
bcc +
|
||||
pha
|
||||
lda #'$'
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
+ jsr conv.ubyte2hex
|
||||
jsr cbm.CHROUT
|
||||
tya
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'%'
|
||||
jsr cbm.CHROUT
|
||||
+ ldy #8
|
||||
- lda #'0'
|
||||
asl P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'1'
|
||||
+ jsr cbm.CHROUT
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr print_ubbin
|
||||
pla
|
||||
clc
|
||||
jmp print_ubbin
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in hexadecimal form (4 digits)
|
||||
; (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr print_ubhex
|
||||
pla
|
||||
clc
|
||||
jmp print_ubhex
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq +
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq _allzero
|
||||
cmp #'0'
|
||||
bne _gotdigit
|
||||
iny
|
||||
bne -
|
||||
|
||||
_gotdigit
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
lda conv.uword2decimal.decTenThousands,y
|
||||
bne _gotdigit
|
||||
rts
|
||||
_allzero
|
||||
lda #'0'
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
|
||||
%asm {{
|
||||
cpy #0
|
||||
bpl +
|
||||
pha
|
||||
lda #'-'
|
||||
jsr cbm.CHROUT
|
||||
tya
|
||||
eor #255
|
||||
tay
|
||||
pla
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
bcc +
|
||||
iny
|
||||
+ jmp print_uw
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
|
||||
; Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0 ; char counter = 0
|
||||
- jsr cbm.CHRIN
|
||||
cmp #$0d ; return (ascii 13) pressed?
|
||||
beq + ; yes, end.
|
||||
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
|
||||
iny
|
||||
bne -
|
||||
+ lda #0
|
||||
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
|
||||
rts
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y) {
|
||||
; ---- sets the character in the screen matrix at the given position
|
||||
%asm {{
|
||||
|
||||
@@ -4,11 +4,7 @@
|
||||
%import shared_floats_functions
|
||||
|
||||
floats {
|
||||
; ---- this block contains C-64 floating point related functions ----
|
||||
const float π = 3.141592653589793
|
||||
const float PI = π
|
||||
const float TWOPI = 2*π
|
||||
|
||||
; ---- this block contains C-64 floating point related functions ----
|
||||
|
||||
; ---- C64 basic and kernal ROM float constants and functions ----
|
||||
|
||||
@@ -24,7 +20,7 @@ romsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value f
|
||||
romsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
|
||||
romsub $ba90 = FAREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac2
|
||||
romsub $bbfc = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||
romsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
||||
romsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded the least significant bit)
|
||||
romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
|
||||
@@ -49,7 +45,7 @@ romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/2
|
||||
romsub $aabc = FPRINTLN() clobbers(A,X,Y) ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
|
||||
romsub $bddd = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY ($0100)
|
||||
|
||||
romsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
|
||||
romsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for integer rounding- call this before INT
|
||||
romsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! You have to fix sign manually!
|
||||
romsub $bc5b = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
@@ -67,7 +63,7 @@ romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mfl
|
||||
romsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
|
||||
romsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
|
||||
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
||||
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to integer round instead of trunc
|
||||
romsub $b9ea = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
||||
romsub $bc39 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||
romsub $bc2b = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||
|
||||
@@ -94,23 +94,29 @@ romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get size of text screen into X (columns) and Y (rows)
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
asmsub STOP2() clobbers(X) -> bool @A {
|
||||
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
|
||||
|
||||
inline asmsub STOP2() clobbers(X,A) -> bool @Pz {
|
||||
; -- just like STOP, but omits the special keys result value in A.
|
||||
; just for convenience because most of the times you're only interested in the stop pressed or not status.
|
||||
%asm {{
|
||||
jsr cbm.STOP
|
||||
beq +
|
||||
lda #0
|
||||
rts
|
||||
+ lda #1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub GETIN2() clobbers(X,Y) -> ubyte @A {
|
||||
; -- just like GETIN, but omits the carry flag result value.
|
||||
; just for convenience because GETIN is so often used to just read keyboard input,
|
||||
; where you don't have to deal with a potential error status
|
||||
%asm {{
|
||||
jsr cbm.GETIN
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
%import syslib
|
||||
%import conv
|
||||
%import shared_textio_functions
|
||||
%import shared_cbm_textio_functions
|
||||
|
||||
txt {
|
||||
|
||||
@@ -13,11 +13,16 @@ txt {
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
sub clear_screen() {
|
||||
chrout(147)
|
||||
}
|
||||
|
||||
sub cls() {
|
||||
chrout(147)
|
||||
}
|
||||
|
||||
sub home() {
|
||||
chrout(19)
|
||||
}
|
||||
@@ -292,216 +297,6 @@ _scroll_screen ; scroll only the screen memory
|
||||
}}
|
||||
}
|
||||
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string, in PESCII encoding, from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to cbm.CHROUT of that single char.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
sty P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_B1),y
|
||||
beq +
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
jsr conv.ubyte2decimal
|
||||
pha
|
||||
tya
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
jsr cbm.CHROUT
|
||||
txa
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
jsr conv.ubyte2decimal
|
||||
_print_byte_digits
|
||||
pha
|
||||
cpy #'0'
|
||||
beq +
|
||||
tya
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
jsr cbm.CHROUT
|
||||
jmp _ones
|
||||
+ pla
|
||||
cmp #'0'
|
||||
beq _ones
|
||||
jsr cbm.CHROUT
|
||||
_ones txa
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the byte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
pha
|
||||
cmp #0
|
||||
bpl +
|
||||
lda #'-'
|
||||
jsr cbm.CHROUT
|
||||
+ pla
|
||||
jsr conv.byte2decimal
|
||||
jmp print_ub._print_byte_digits
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
bcc +
|
||||
pha
|
||||
lda #'$'
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
+ jsr conv.ubyte2hex
|
||||
jsr cbm.CHROUT
|
||||
tya
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'%'
|
||||
jsr cbm.CHROUT
|
||||
+ ldy #8
|
||||
- lda #'0'
|
||||
asl P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'1'
|
||||
+ jsr cbm.CHROUT
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr print_ubbin
|
||||
pla
|
||||
clc
|
||||
jmp print_ubbin
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in hexadecimal form (4 digits)
|
||||
; (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr print_ubhex
|
||||
pla
|
||||
clc
|
||||
jmp print_ubhex
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq +
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq _allzero
|
||||
cmp #'0'
|
||||
bne _gotdigit
|
||||
iny
|
||||
bne -
|
||||
|
||||
_gotdigit
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
lda conv.uword2decimal.decTenThousands,y
|
||||
bne _gotdigit
|
||||
rts
|
||||
_allzero
|
||||
lda #'0'
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
|
||||
%asm {{
|
||||
cpy #0
|
||||
bpl +
|
||||
pha
|
||||
lda #'-'
|
||||
jsr cbm.CHROUT
|
||||
tya
|
||||
eor #255
|
||||
tay
|
||||
pla
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
bcc +
|
||||
iny
|
||||
+ jmp print_uw
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
|
||||
; Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0 ; char counter = 0
|
||||
- jsr cbm.CHRIN
|
||||
cmp #$0d ; return (ascii 13) pressed?
|
||||
beq + ; yes, end.
|
||||
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
|
||||
iny
|
||||
bne -
|
||||
+ lda #0
|
||||
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
|
||||
rts
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y) {
|
||||
; ---- sets the character in the screen matrix at the given position
|
||||
%asm {{
|
||||
|
||||
+95
-105
@@ -8,97 +8,79 @@ conv {
|
||||
|
||||
str @shared string_out = "????????????????" ; result buffer for the string conversion routines
|
||||
|
||||
asmsub str_ub0 (ubyte value @ A) clobbers(X) -> str @AY {
|
||||
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
jsr conv.ubyte2decimal
|
||||
sty string_out
|
||||
sta string_out+1
|
||||
stx string_out+2
|
||||
lda #0
|
||||
sta string_out+3
|
||||
lda #<string_out
|
||||
ldy #>string_out
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub str_ub0(ubyte value @A) clobbers(X) -> str @AY {
|
||||
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
jsr internal_ubyte2decimal
|
||||
sty conv.string_out
|
||||
stx conv.string_out+1
|
||||
sta conv.string_out+2
|
||||
lda #0
|
||||
sta conv.string_out+3
|
||||
lda #<conv.string_out
|
||||
ldy #>conv.string_out
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_ub (ubyte value @ A) clobbers(X) -> str @AY {
|
||||
; ---- convert the ubyte in A in decimal string form, without left padding 0s
|
||||
%asm {{
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_B1
|
||||
jsr ubyte2decimal ; result in Y/A/X (100s, 10s, 1s).
|
||||
; hundreds?
|
||||
cpy #'0'
|
||||
beq +
|
||||
sty string_out
|
||||
sta string_out+1
|
||||
stx string_out+2
|
||||
lda #0
|
||||
sta string_out+3
|
||||
jmp _done
|
||||
; tens?
|
||||
+ cmp #'0'
|
||||
beq +
|
||||
sta string_out
|
||||
stx string_out+1
|
||||
lda #0
|
||||
sta string_out+2
|
||||
jmp _done
|
||||
+ ; ones.
|
||||
stx string_out
|
||||
lda #0
|
||||
sta string_out+1
|
||||
_done lda #<string_out
|
||||
ldy #>string_out
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub str_ub(ubyte value @A) clobbers(X) -> str @AY {
|
||||
; ---- convert the ubyte in A in decimal string form, without left padding 0s
|
||||
%asm {{
|
||||
jsr internal_ubyte2decimal
|
||||
cpy #'0'
|
||||
beq +
|
||||
sty conv.string_out
|
||||
stx conv.string_out+1
|
||||
sta conv.string_out+2
|
||||
lda #0
|
||||
sta conv.string_out+3
|
||||
jmp _done
|
||||
+ cpx #'0'
|
||||
beq +
|
||||
stx conv.string_out
|
||||
sta conv.string_out+1
|
||||
lda #0
|
||||
sta conv.string_out+2
|
||||
jmp _done
|
||||
+ sta conv.string_out
|
||||
lda #0
|
||||
sta conv.string_out+1
|
||||
_done lda #<conv.string_out
|
||||
ldy #>conv.string_out
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_b (byte value @ A) clobbers(X) -> str @AY {
|
||||
; ---- convert the byte in A in decimal string form, without left padding 0s
|
||||
%asm {{
|
||||
ldy #0
|
||||
cmp #0
|
||||
bpl +
|
||||
ldy #'-'
|
||||
sty string_out
|
||||
ldy #1
|
||||
+ sty P8ZP_SCRATCH_REG
|
||||
jsr conv.byte2decimal ; result in Y/A/X (100s, 10s, 1s). and in uword2decimal.decHundreds, decTens, decOnes.
|
||||
; hundreds?
|
||||
cpy #'0'
|
||||
bne _out_hundreds
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
cmp #'0'
|
||||
bne _out_tens
|
||||
beq _out_ones
|
||||
_out_hundreds
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
lda uword2decimal.decHundreds
|
||||
sta string_out,y
|
||||
iny
|
||||
_out_tens
|
||||
lda uword2decimal.decTens
|
||||
sta string_out,y
|
||||
iny
|
||||
_out_ones
|
||||
lda uword2decimal.decOnes
|
||||
sta string_out,y
|
||||
iny
|
||||
lda #0
|
||||
sta string_out,y
|
||||
lda #<string_out
|
||||
ldy #>string_out
|
||||
rts
|
||||
}}
|
||||
}
|
||||
asmsub str_b(byte value @A) clobbers(X) -> str @AY {
|
||||
; ---- convert the byte in A in decimal string form, without left padding 0s
|
||||
%asm {{
|
||||
cmp #0
|
||||
bpl str_ub
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
jsr str_ub
|
||||
; insert a minus sign at the start
|
||||
lda #0
|
||||
sta conv.string_out+4
|
||||
lda conv.string_out+2
|
||||
sta conv.string_out+3
|
||||
lda conv.string_out+1
|
||||
sta conv.string_out+2
|
||||
lda conv.string_out
|
||||
sta conv.string_out+1
|
||||
lda #'-'
|
||||
sta conv.string_out
|
||||
lda #<conv.string_out
|
||||
ldy #>conv.string_out
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_ubhex (ubyte value @ A) clobbers(X) -> str @AY {
|
||||
; ---- convert the ubyte in A in hex string form
|
||||
%asm {{
|
||||
jsr conv.ubyte2hex
|
||||
jsr internal_ubyte2hex
|
||||
sta string_out
|
||||
sty string_out+1
|
||||
lda #0
|
||||
@@ -158,11 +140,11 @@ asmsub str_uwhex (uword value @ AY) -> str @AY {
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr conv.ubyte2hex
|
||||
jsr internal_ubyte2hex
|
||||
sta string_out
|
||||
sty string_out+1
|
||||
pla
|
||||
jsr conv.ubyte2hex
|
||||
jsr internal_ubyte2hex
|
||||
sta string_out+2
|
||||
sty string_out+3
|
||||
lda #0
|
||||
@@ -176,9 +158,9 @@ asmsub str_uwhex (uword value @ AY) -> str @AY {
|
||||
asmsub str_uw0 (uword value @ AY) clobbers(X) -> str @AY {
|
||||
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
jsr conv.uword2decimal
|
||||
jsr conv.internal_uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
- lda conv.internal_uword2decimal.decTenThousands,y
|
||||
sta string_out,y
|
||||
beq +
|
||||
iny
|
||||
@@ -193,11 +175,11 @@ asmsub str_uw0 (uword value @ AY) clobbers(X) -> str @AY {
|
||||
asmsub str_uw (uword value @ AY) clobbers(X) -> str @AY {
|
||||
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
|
||||
%asm {{
|
||||
jsr conv.uword2decimal
|
||||
jsr conv.internal_uword2decimal
|
||||
ldx #0
|
||||
_output_digits
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
- lda internal_uword2decimal.decTenThousands,y
|
||||
beq _allzero
|
||||
cmp #'0'
|
||||
bne _gotdigit
|
||||
@@ -206,7 +188,7 @@ _output_digits
|
||||
_gotdigit sta string_out,x
|
||||
inx
|
||||
iny
|
||||
lda conv.uword2decimal.decTenThousands,y
|
||||
lda internal_uword2decimal.decTenThousands,y
|
||||
bne _gotdigit
|
||||
_end lda #0
|
||||
sta string_out,x
|
||||
@@ -238,7 +220,7 @@ asmsub str_w (word value @ AY) clobbers(X) -> str @AY {
|
||||
adc #1
|
||||
bcc +
|
||||
iny
|
||||
+ jsr conv.uword2decimal
|
||||
+ jsr conv.internal_uword2decimal
|
||||
ldx #1
|
||||
bne str_uw._output_digits
|
||||
rts
|
||||
@@ -533,17 +515,25 @@ _stop
|
||||
|
||||
; ----- low level number conversions to decimal strings ----
|
||||
|
||||
asmsub ubyte2decimal (ubyte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
|
||||
%asm {{
|
||||
ldy #uword2decimal.ASCII_0_OFFSET
|
||||
jmp uword2decimal.hex_try200
|
||||
}}
|
||||
asmsub internal_ubyte2decimal(ubyte value @A) -> ubyte @Y, ubyte @X, ubyte @A {
|
||||
%asm {{
|
||||
ldy #'0'-1
|
||||
ldx #'9'+1
|
||||
sec
|
||||
- iny
|
||||
sbc #100
|
||||
bcs -
|
||||
- dex
|
||||
adc #10
|
||||
bmi -
|
||||
adc #'0'-1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
asmsub internal_uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
; ---- convert 16 bit uword in A/Y to decimal
|
||||
; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
|
||||
; output in internal_uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
|
||||
; (these are terminated by a zero byte so they can be easily printed)
|
||||
; also returns Y = 100's, A = 10's, X = 1's
|
||||
|
||||
@@ -716,7 +706,7 @@ decOnes .byte 0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
asmsub internal_byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
|
||||
; note: if the number is negative, you have to deal with the '-' yourself!
|
||||
%asm {{
|
||||
@@ -725,11 +715,11 @@ asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
+ jmp ubyte2decimal
|
||||
+ jmp internal_ubyte2decimal
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub ubyte2hex (ubyte value @A) clobbers(X) -> ubyte @A, ubyte @Y {
|
||||
asmsub internal_ubyte2hex (ubyte value @A) clobbers(X) -> ubyte @A, ubyte @Y {
|
||||
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
|
||||
%asm {{
|
||||
pha
|
||||
@@ -749,7 +739,7 @@ _hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub uword2hex (uword value @AY) clobbers(A,Y) {
|
||||
asmsub internal_uword2hex (uword value @AY) clobbers(A,Y) {
|
||||
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG
|
||||
|
||||
@@ -170,6 +170,7 @@ save_end:
|
||||
sub set_bpp(ubyte bpp) {
|
||||
ubyte[8] depths = [0,1,1,2,2,2,2,3]
|
||||
vera_colordepth = depths[bpp-1]
|
||||
bitsperpixel = bpp
|
||||
}
|
||||
|
||||
sub set_vera_colordepth(ubyte depth) {
|
||||
@@ -234,7 +235,7 @@ save_end:
|
||||
|
||||
sub read_scanline(uword size) {
|
||||
while size!=0 {
|
||||
cx16.r0 = cx16.MACPTR(min(255, size) as ubyte, &cx16.VERA_DATA0, true)
|
||||
void, cx16.r0 = cx16.MACPTR(min(255, size) as ubyte, &cx16.VERA_DATA0, true)
|
||||
if_cs {
|
||||
; no MACPTR support
|
||||
repeat size
|
||||
@@ -296,7 +297,7 @@ save_end:
|
||||
cx16.r0L = lsb(size)
|
||||
if msb(size)!=0
|
||||
cx16.r0L = 0 ; 256 bytes
|
||||
cx16.r0 = cx16.MCIOUT(cx16.r0L, &cx16.VERA_DATA0, true)
|
||||
void, cx16.r0 = cx16.MCIOUT(cx16.r0L, &cx16.VERA_DATA0, true)
|
||||
if_cs {
|
||||
; no MCIOUT support
|
||||
repeat size
|
||||
|
||||
@@ -32,6 +32,19 @@ diskio {
|
||||
cbm.CHKOUT(WRITE_IO_CHANNEL)
|
||||
}
|
||||
|
||||
sub fastmode(ubyte mode) -> bool {
|
||||
; -- set fast serial mode (0=none, 1=auto_tx, 2=fast writes, 3=both) for the SD card.
|
||||
; Returns success status (fails on emulator host fs for example)
|
||||
list_filename[0] = 'u'
|
||||
list_filename[1] = '0'
|
||||
list_filename[2] = '>'
|
||||
list_filename[3] = 'b'
|
||||
list_filename[4] = mode | $30
|
||||
list_filename[5] = 0
|
||||
send_command(list_filename)
|
||||
return status_code()==0
|
||||
}
|
||||
|
||||
sub directory() -> bool {
|
||||
; -- Prints the directory contents to the screen. Returns success.
|
||||
|
||||
@@ -70,7 +83,8 @@ diskio {
|
||||
void cbm.CHRIN() ; skip 2 bytes
|
||||
void cbm.CHRIN()
|
||||
status = cbm.READST()
|
||||
if cbm.STOP2()
|
||||
void cbm.STOP()
|
||||
if_z
|
||||
break
|
||||
}
|
||||
status = cbm.READST()
|
||||
@@ -319,7 +333,7 @@ close_end:
|
||||
readsize = 255
|
||||
if num_bytes<readsize
|
||||
readsize = num_bytes
|
||||
readsize = cx16.MACPTR(lsb(readsize), bufferpointer, false) ; fast block reads
|
||||
void, readsize = cx16.MACPTR(lsb(readsize), bufferpointer, false) ; fast block reads
|
||||
if_cs
|
||||
goto byte_read_loop ; MACPTR block read not supported, do fallback loop
|
||||
list_blocks += readsize
|
||||
@@ -456,7 +470,7 @@ _end rts
|
||||
if num_bytes!=0 {
|
||||
reset_write_channel()
|
||||
do {
|
||||
cx16.r0 = cx16.MCIOUT(lsb(num_bytes), bufferpointer, false) ; fast block writes
|
||||
void, cx16.r0 = cx16.MCIOUT(lsb(num_bytes), bufferpointer, false) ; fast block writes
|
||||
if_cs
|
||||
goto no_mciout
|
||||
num_bytes -= cx16.r0
|
||||
@@ -525,6 +539,39 @@ io_error:
|
||||
goto done
|
||||
}
|
||||
|
||||
; similar to above, but instead of fetching the entire string, it only fetches the status code and returns it as ubyte
|
||||
; in case of IO error, returns 255 (CMDR-DOS itself is physically unable to return such a value)
|
||||
sub status_code() -> ubyte {
|
||||
if cbm.READST()==128 {
|
||||
return 255
|
||||
}
|
||||
|
||||
cbm.SETNAM(0, list_filename)
|
||||
cbm.SETLFS(15, drivenumber, 15)
|
||||
void cbm.OPEN() ; open 15,8,15
|
||||
if_cs
|
||||
goto io_error
|
||||
void cbm.CHKIN(15) ; use #15 as input channel
|
||||
|
||||
list_filename[0] = cbm.CHRIN()
|
||||
list_filename[1] = cbm.CHRIN()
|
||||
list_filename[2] = 0
|
||||
|
||||
while cbm.READST()==0 {
|
||||
void cbm.CHRIN()
|
||||
}
|
||||
|
||||
cbm.CLRCHN() ; restore default i/o devices
|
||||
cbm.CLOSE(15)
|
||||
return conv.str2ubyte(list_filename)
|
||||
|
||||
io_error:
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(15)
|
||||
return 255
|
||||
}
|
||||
|
||||
|
||||
|
||||
; saves a block of memory to disk, including the default 2 byte prg header.
|
||||
sub save(uword filenameptr, uword startaddress, uword savesize) -> bool {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
; Emulator debug interface.
|
||||
; Docs: https://github.com/X16Community/x16-emulator#emulator-io-registers
|
||||
; Docs: https://github.com/X16Community/x16-emulator/tree/d52f118ce893fa24c4ca021a0b8de46cb80e5ccf#emulator-io-registers
|
||||
|
||||
emudbg {
|
||||
%option ignore_unused
|
||||
@@ -41,7 +41,7 @@ emudbg {
|
||||
chr = @(isoString)
|
||||
if_z
|
||||
break
|
||||
EMU_CPUCLK_U = chr
|
||||
EMU_CHROUT = chr
|
||||
isoString++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,7 @@
|
||||
%import shared_floats_functions
|
||||
|
||||
floats {
|
||||
; ---- this block contains C-64 compatible floating point related functions ----
|
||||
|
||||
const float π = 3.141592653589793
|
||||
const float PI = π
|
||||
const float TWOPI = 2*π
|
||||
|
||||
; ---- this block contains C-64 compatible floating point related functions ----
|
||||
|
||||
; ---- ROM float functions (same as on C128 except base page) ----
|
||||
|
||||
@@ -19,7 +14,7 @@ floats {
|
||||
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
||||
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
||||
|
||||
romsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
|
||||
romsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 'facmo' and 'faclo', MSB FIRST. (might throw ILLEGAL QUANTITY) See "basic.sym" kernal symbol file for their memory locations.
|
||||
|
||||
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
||||
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
||||
@@ -43,7 +38,7 @@ romsub $fe21 = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||
romsub $fe24 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1
|
||||
romsub $fe27 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands
|
||||
romsub $fe2a = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
||||
romsub $fe2d = INT() clobbers(A,X,Y) ; INT() truncates, use ROUND or FADDH first to round instead of trunc
|
||||
romsub $fe2d = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to integer round instead of trunc
|
||||
romsub $fe30 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||
romsub $fe33 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
|
||||
romsub $fe36 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** float in A/Y
|
||||
@@ -53,7 +48,7 @@ romsub $fe3f = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||
romsub $fe42 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||
romsub $fe45 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||
romsub $fe48 = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
||||
romsub $fe4b = ROUND() clobbers(A,X,Y) ; round fac1
|
||||
romsub $fe4b = ROUND() clobbers(A,X,Y) ; round least significant bit of fac1
|
||||
romsub $fe4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
|
||||
romsub $fe51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||
romsub $fe54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
@@ -65,10 +60,10 @@ romsub $fe60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value f
|
||||
romsub $fe63 = MOVFM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1
|
||||
romsub $fe66 = MOVMF(uword mflpt @ XY) clobbers(A,X,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
romsub $fe69 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||
romsub $fe6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
||||
romsub $fe6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded the least significant bit)
|
||||
|
||||
; X16 additions
|
||||
romsub $fe6f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
|
||||
romsub $fe6f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for integer rounding- call this before INT
|
||||
romsub $fe72 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
|
||||
romsub $fe75 = NORMAL() clobbers(A,X,Y) ; normalize fac1
|
||||
romsub $fe78 = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (doesn't work, juse use NEGOP() instead!)
|
||||
@@ -98,8 +93,8 @@ asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
|
||||
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
||||
%asm {{
|
||||
sty $c4 ; facmo ($64 on c128)
|
||||
sta $c5 ; facmo+1 ($65 on c128)
|
||||
sty $c4 ; facmo
|
||||
sta $c5 ; facmo+1
|
||||
ldx #$90
|
||||
sec
|
||||
jmp FLOATC
|
||||
@@ -136,14 +131,12 @@ asmsub FREADUY (ubyte value @Y) {
|
||||
}
|
||||
|
||||
asmsub parse(str value @AY) -> float @FAC1 {
|
||||
; -- parse a string value of a number to float in FAC1
|
||||
; warning: on older <R47 kernals it uses an internal BASIC routine that is ROM version dependent,
|
||||
; ($deb6 is inside the routine for VAL at $deb3) See basic.sym from x16-rom
|
||||
; TODO once ROM v47 is released, all the workarounds here can be removed. But probably keep the kernal VAL_1 existance check
|
||||
; -- Parse a string value of a number to float in FAC1.
|
||||
; Requires kernal R47 or newer! (depends on val_1 working)
|
||||
%asm {{
|
||||
ldx VAL_1
|
||||
cpx #$4c ; is there an implementation in VAL_1? (test for JMP)
|
||||
bne + ; no, do it ourselves
|
||||
bne _borked ; no, print error message
|
||||
pha ; yes, count the length and call rom VAL_1.
|
||||
phy
|
||||
jsr prog8_lib.strlen
|
||||
@@ -151,15 +144,7 @@ asmsub parse(str value @AY) -> float @FAC1 {
|
||||
ply
|
||||
plx
|
||||
jmp VAL_1
|
||||
+ sta $a9 ; 'index' variable
|
||||
sty $aa
|
||||
jsr prog8_lib.strlen
|
||||
lda $deb6
|
||||
cmp #$d0 ; sanity check for kernal routine correct
|
||||
bne +
|
||||
tya
|
||||
jmp $deb6 ; kernal version dependent...
|
||||
+ ; print error message if routine is borked in kernal, and exit program
|
||||
_borked
|
||||
ldy #0
|
||||
- lda _msg,y
|
||||
beq +
|
||||
@@ -168,12 +153,12 @@ asmsub parse(str value @AY) -> float @FAC1 {
|
||||
bne -
|
||||
+ jmp sys.exit
|
||||
|
||||
_msg .text 13,"?val kaputt",13,0
|
||||
_msg .text 13,"?rom 47+ required for val1",13,0
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
&uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT
|
||||
&uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT (See "basic.sym" kernal symbol file)
|
||||
|
||||
sub rnd() -> float {
|
||||
%asm {{
|
||||
|
||||
@@ -48,7 +48,7 @@ psg {
|
||||
sub freq(ubyte voice_num, uword vera_freq) {
|
||||
; -- Changes the frequency of the voice's sound.
|
||||
; voice_num = 0-15, vera_freq = 0-65535 calculate this via the formula given in the Vera's PSG documentation.
|
||||
; (https://github.com/x16community/x16-docs/blob/master/VERA%20Programmer's%20Reference.md)
|
||||
; (https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2009%20-%20VERA%20Programmer's%20Reference.md)
|
||||
; Write freq MSB first and then LSB to reduce the chance on clicks
|
||||
sys.irqsafe_set_irqd()
|
||||
cx16.r0 = $f9c1 + voice_num * 4
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
; note: sprites z-order will be in front of all layers.
|
||||
; note: collision mask is not supported here yet.
|
||||
; note: "palette offset" is counted as 0-15 (vera multiplies the offset by 16 to get at the actual color index)
|
||||
|
||||
sprites {
|
||||
%option ignore_unused
|
||||
@@ -21,7 +22,7 @@ sprites {
|
||||
sub init(ubyte spritenum,
|
||||
ubyte databank, uword dataaddr,
|
||||
ubyte width_flag, ubyte height_flag,
|
||||
ubyte colors_flag, ubyte palette_offset_idx) {
|
||||
ubyte colors_flag, ubyte palette_offset) {
|
||||
hide(spritenum)
|
||||
cx16.VERA_DC_VIDEO |= %01000000 ; enable sprites globally
|
||||
dataaddr >>= 5
|
||||
@@ -30,7 +31,7 @@ sprites {
|
||||
cx16.vpoke(1, sprite_reg, lsb(dataaddr)) ; address 12:5
|
||||
cx16.vpoke(1, sprite_reg+1, colors_flag | msb(dataaddr)) ; 4 bpp + address 16:13
|
||||
cx16.vpoke(1, sprite_reg+6, %00001100) ; z depth %11 = in front of both layers, no flips
|
||||
cx16.vpoke(1, sprite_reg+7, height_flag<<6 | width_flag<<4 | palette_offset_idx>>4) ; 64x64 pixels, palette offset
|
||||
cx16.vpoke(1, sprite_reg+7, height_flag<<6 | width_flag<<4 | palette_offset&15) ; 64x64 pixels, palette offset
|
||||
}
|
||||
|
||||
sub data(ubyte spritenum, ubyte bank, uword addr) {
|
||||
@@ -143,7 +144,7 @@ sprites {
|
||||
}
|
||||
|
||||
sub set_palette_offset(ubyte spritenum, ubyte offset) {
|
||||
cx16.vpoke_mask(1, VERA_SPRITEREGS + 7 + spritenum*$0008, %11110000, offset>>4)
|
||||
cx16.vpoke_mask(1, VERA_SPRITEREGS + 7 + spritenum*$0008, %11110000, offset&15)
|
||||
}
|
||||
|
||||
sub set_mousepointer_hand() {
|
||||
|
||||
@@ -19,7 +19,7 @@ romsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; rea
|
||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set top of memory pointer. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See cx16.numbanks()
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY, ubyte @A ; read/set top of memory pointer. NOTE: on the Cx16 also returns the number of RAM memory banks in A! Also see cx16.numbanks()
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard, also called kbd_scan
|
||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
@@ -32,7 +32,7 @@ romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word (use CLEARST to reset it to 0)
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
@@ -42,37 +42,50 @@ romsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device. See also BSAVE
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (in little endian order: A=lo,X=mid,Y=high) , however use RDTIM_safe() instead
|
||||
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get size of text screen into X (columns) and Y (rows)
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Also see txt.plot
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
; ---- utility
|
||||
|
||||
asmsub STOP2() clobbers(X) -> bool @A {
|
||||
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the zero status flag.
|
||||
inline asmsub STOP2() clobbers(X,A) -> bool @Pz {
|
||||
; -- just like STOP, but omits the special keys result value in A.
|
||||
; just for convenience because most of the times you're only interested in the stop pressed or not status.
|
||||
%asm {{
|
||||
jsr cbm.STOP
|
||||
beq +
|
||||
lda #0
|
||||
rts
|
||||
+ lda #1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIM16() clobbers(X) -> uword @AY {
|
||||
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience. Also avoids ram bank issue for irqs.
|
||||
inline asmsub GETIN2() clobbers(X,Y) -> ubyte @A {
|
||||
; -- just like GETIN, but omits the carry flag result value.
|
||||
; just for convenience because GETIN is so often used to just read keyboard input,
|
||||
; where you don't have to deal with a potential error status
|
||||
%asm {{
|
||||
jsr cbm.GETIN
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIM_safe() -> ubyte @ A, ubyte @ X, ubyte @ Y {
|
||||
; -- read the software clock (in little endian order: A=lo,X=mid,Y=high)
|
||||
; with safeguard for ram bank issue for irqs.
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
jsr cbm.RDTIM
|
||||
plp
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIM16() clobbers(X) -> uword @AY {
|
||||
; -- like RDTIM_safe() but only returning the lower 16 bits in AY for convenience.
|
||||
%asm {{
|
||||
jsr RDTIM_safe
|
||||
pha
|
||||
txa
|
||||
tay
|
||||
@@ -295,33 +308,33 @@ cx16 {
|
||||
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F
|
||||
|
||||
; Vera FX registers: (accessing depends on particular DCSEL value set in VERA_CTRL!)
|
||||
&ubyte VERA_FX_CTRL = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_TILEBASE = VERA_BASE + $000a
|
||||
&ubyte VERA_FX_MAPBASE = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_MULT = VERA_BASE + $000c
|
||||
&ubyte VERA_FX_X_INCR_L = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_X_INCR_H = VERA_BASE + $000a
|
||||
&uword VERA_FX_X_INCR = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_Y_INCR_L = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_Y_INCR_H = VERA_BASE + $000c
|
||||
&uword VERA_FX_Y_INCR = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_X_POS_L = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_X_POS_H = VERA_BASE + $000a
|
||||
&uword VERA_FX_X_POS = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_Y_POS_L = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_Y_POS_H = VERA_BASE + $000c
|
||||
&uword VERA_FX_Y_POS = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_X_POS_S = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_Y_POS_S = VERA_BASE + $000a
|
||||
&ubyte VERA_FX_POLY_FILL_L = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_POLY_FILL_H = VERA_BASE + $000c
|
||||
&uword VERA_FX_POLY_FILL = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_CACHE_L = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_CACHE_M = VERA_BASE + $000a
|
||||
&ubyte VERA_FX_CACHE_H = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_CACHE_U = VERA_BASE + $000c
|
||||
&ubyte VERA_FX_ACCUM = VERA_BASE + $000a
|
||||
&ubyte VERA_FX_ACCUM_RESET = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_CTRL = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_TILEBASE = VERA_BASE + $000a
|
||||
&ubyte VERA_FX_MAPBASE = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_MULT = VERA_BASE + $000c
|
||||
&ubyte VERA_FX_X_INCR_L = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_X_INCR_H = VERA_BASE + $000a
|
||||
&uword VERA_FX_X_INCR = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_Y_INCR_L = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_Y_INCR_H = VERA_BASE + $000c
|
||||
&uword VERA_FX_Y_INCR = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_X_POS_L = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_X_POS_H = VERA_BASE + $000a
|
||||
&uword VERA_FX_X_POS = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_Y_POS_L = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_Y_POS_H = VERA_BASE + $000c
|
||||
&uword VERA_FX_Y_POS = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_X_POS_S = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_Y_POS_S = VERA_BASE + $000a
|
||||
&ubyte VERA_FX_POLY_FILL_L = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_POLY_FILL_H = VERA_BASE + $000c
|
||||
&uword VERA_FX_POLY_FILL = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_CACHE_L = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_CACHE_M = VERA_BASE + $000a
|
||||
&ubyte VERA_FX_CACHE_H = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_CACHE_U = VERA_BASE + $000c
|
||||
&ubyte VERA_FX_ACCUM = VERA_BASE + $000a
|
||||
&ubyte VERA_FX_ACCUM_RESET = VERA_BASE + $0009
|
||||
|
||||
|
||||
; VERA_PSG_BASE = $1F9C0
|
||||
@@ -376,20 +389,16 @@ cx16 {
|
||||
; ---- Commander X-16 additions on top of C64 kernal routines ----
|
||||
; spelling of the names is taken from the Commander X-16 rom sources
|
||||
|
||||
; supported C128 additions
|
||||
romsub $ff4a = CLOSE_ALL(ubyte device @A) clobbers(A,X,Y)
|
||||
romsub $ff59 = LKUPLA(ubyte la @A) clobbers(A,X,Y)
|
||||
romsub $ff5c = LKUPSA(ubyte sa @Y) clobbers(A,X,Y)
|
||||
romsub $ff5f = screen_mode(ubyte mode @A, bool getCurrent @Pc) clobbers(X, Y) -> ubyte @A, bool @Pc ; note: X,Y size result is not supported, use SCREEN or get_screen_mode routine for that
|
||||
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y) ; incompatible with C128 dlchr()
|
||||
; not yet supported: romsub $ff65 = pfkey() clobbers(A,X,Y)
|
||||
romsub $ff5f = screen_mode(ubyte mode @A, bool getCurrent @Pc) -> ubyte @A, ubyte @X, ubyte @Y, bool @Pc ; also see SCREEN or get_screen_mode()
|
||||
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y)
|
||||
romsub $ff6e = JSRFAR() ; following word = address to call, byte after that=rom/ram bank it is in
|
||||
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
|
||||
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||
romsub $ff7d = PRIMM()
|
||||
|
||||
; It's not documented what registers are clobbered, so we assume the worst for all following kernal routines...:
|
||||
|
||||
; high level graphics & fonts
|
||||
romsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
|
||||
romsub $ff23 = GRAPH_clear() clobbers(A,X,Y)
|
||||
@@ -422,9 +431,11 @@ romsub $ff1a = FB_filter_pixels(uword pointer @ R0, uword count @R1) clobbers(A
|
||||
romsub $ff1d = FB_move_pixels(uword sx @R0, uword sy @R1, uword tx @R2, uword ty @R3, uword count @R4) clobbers(A,X,Y)
|
||||
|
||||
; misc
|
||||
romsub $FEBA = BSAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; like cbm.SAVE, but omits the 2-byte prg header
|
||||
romsub $fec6 = i2c_read_byte(ubyte device @X, ubyte offset @Y) clobbers (X,Y) -> ubyte @A, bool @Pc
|
||||
romsub $fec9 = i2c_write_byte(ubyte device @X, ubyte offset @Y, ubyte data @A) clobbers (A,X,Y) -> bool @Pc
|
||||
romsub $feb4 = i2c_batch_read(ubyte device @X, uword buffer @R0, uword length @R1, bool advance @Pc) clobbers(A,Y) -> bool @Pc
|
||||
romsub $feb7 = i2c_batch_write(ubyte device @X, uword buffer @R0, uword length @R1, bool advance @Pc) clobbers(A,Y) -> bool @Pc
|
||||
|
||||
romsub $fef0 = sprite_set_image(uword pixels @R0, uword mask @R1, ubyte bpp @R2, ubyte number @A, ubyte width @X, ubyte height @Y, bool apply_mask @Pc) clobbers(A,X,Y) -> bool @Pc
|
||||
romsub $fef3 = sprite_set_position(uword x @R0, uword y @R1, ubyte number @A) clobbers(A,X,Y)
|
||||
romsub $fee4 = memory_fill(uword address @R0, uword num_bytes @R1, ubyte value @A) clobbers(A,X,Y)
|
||||
@@ -437,10 +448,13 @@ romsub $fee1 = console_get_char() clobbers(X,Y) -> ubyte @A
|
||||
romsub $fed8 = console_put_image(uword pointer @R0, uword width @R1, uword height @R2) clobbers(A,X,Y)
|
||||
romsub $fed5 = console_set_paging_message(uword msgptr @R0) clobbers(A,X,Y)
|
||||
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
|
||||
;; romsub $fea8 = extapi16(ubyte callnumber @A) clobbers (A,X,Y) ; not useful yet because is for 65816 cpu
|
||||
romsub $feab = extapi(ubyte callnumber @A) clobbers (A,X,Y)
|
||||
romsub $fecc = monitor() clobbers(A,X,Y)
|
||||
|
||||
romsub $ff44 = MACPTR(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
|
||||
romsub $feb1 = MCIOUT(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
|
||||
romsub $FEBA = BSAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; like cbm.SAVE, but omits the 2-byte prg header
|
||||
romsub $ff47 = enter_basic(bool cold_or_warm @Pc) clobbers(A,X,Y)
|
||||
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, uword jiffiesweekday @R3) clobbers(A, X, Y)
|
||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, uword @R3 ; result registers see clock_set_date_time()
|
||||
@@ -448,16 +462,14 @@ romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1
|
||||
; keyboard, mouse, joystick
|
||||
; note: also see the cbm.kbdbuf_clear() helper routine
|
||||
romsub $febd = kbdbuf_peek() -> ubyte @A, ubyte @X ; key in A, queue length in X
|
||||
romsub $febd = kbdbuf_peek2() -> uword @AX ; alternative to above to not have the hassle to deal with multiple return values
|
||||
romsub $fec0 = kbdbuf_get_modifiers() -> ubyte @A
|
||||
romsub $fec3 = kbdbuf_put(ubyte key @A) clobbers(X)
|
||||
romsub $fed2 = keymap(uword identifier @XY, bool read @Pc) -> bool @Pc
|
||||
romsub $ff68 = mouse_config(byte shape @A, ubyte resX @X, ubyte resY @Y) clobbers (A, X, Y)
|
||||
romsub $ff6b = mouse_get(ubyte zpdataptr @X) -> ubyte @A
|
||||
romsub $ff6b = mouse_get(ubyte zpdataptr @X) -> ubyte @A ; use mouse_pos() instead
|
||||
romsub $ff71 = mouse_scan() clobbers(A, X, Y)
|
||||
romsub $ff53 = joystick_scan() clobbers(A, X, Y)
|
||||
romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
|
||||
romsub $ff56 = joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX ; alternative to above to not have the hassle to deal with multiple return values
|
||||
romsub $ff56 = joystick_get(ubyte joynr @A) -> uword @AX, bool @Y ; note: everything is inverted
|
||||
|
||||
; X16Edit (rom bank 13/14 but you ideally should use the routine search_x16edit() to search for the correct bank)
|
||||
romsub $C000 = x16edit_default() clobbers(A,X,Y)
|
||||
@@ -524,6 +536,27 @@ romsub $C099 = ym_getatten(ubyte channel @A) clobbers(Y) -> ubyte @X
|
||||
romsub $C09C = ym_getpan(ubyte channel @A) clobbers(Y) -> ubyte @X
|
||||
romsub $C0A5 = ym_get_chip_type() clobbers(X) -> ubyte @A
|
||||
|
||||
; extapi call numbers
|
||||
const ubyte EXTAPI_clear_status = $01
|
||||
const ubyte EXTAPI_getlfs = $02
|
||||
const ubyte EXTAPI_mouse_sprite_offset = $03
|
||||
const ubyte EXTAPI_joystick_ps2_keycodes = $04
|
||||
const ubyte EXTAPI_iso_cursor_char = $05
|
||||
const ubyte EXTAPI_ps2kbd_typematic = $06
|
||||
const ubyte EXTAPI_pfkey = $07
|
||||
const ubyte EXTAPI_ps2data_fetch = $08
|
||||
const ubyte EXTAPI_ps2data_raw = $09
|
||||
const ubyte EXTAPI_cursor_blink = $0A
|
||||
const ubyte EXTAPI_led_update = $0B
|
||||
const ubyte EXTAPI_mouse_set_position = $0C
|
||||
|
||||
; extapi16 call numbers
|
||||
const ubyte EXTAPI16_test = $00
|
||||
const ubyte EXTAPI16_stack_push = $01
|
||||
const ubyte EXTAPI16_stack_pop = $02
|
||||
const ubyte EXTAPI16_stack_enter_kernal_stack = $03
|
||||
const ubyte EXTAPI16_stack_leave_kernal_stack = $04
|
||||
|
||||
|
||||
asmsub set_screen_mode(ubyte mode @A) clobbers(A,X,Y) -> bool @Pc {
|
||||
; -- convenience wrapper for screen_mode() to just set a new mode (and return success)
|
||||
@@ -533,9 +566,8 @@ asmsub set_screen_mode(ubyte mode @A) clobbers(A,X,Y) -> bool @Pc {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_screen_mode() -> byte @A, byte @X, byte @Y {
|
||||
; -- convenience wrapper for screen_mode() to just get the current mode in A, and size in characters in X+Y
|
||||
; this does need a piece of inlined asm to call it ans store the result values if you call this from prog8 code
|
||||
asmsub get_screen_mode() -> ubyte @A, ubyte @X, ubyte @Y {
|
||||
; -- convenience wrapper for screen_mode() to just get the current mode in A, and size in characters in X (width) and Y (height)
|
||||
; Note: you can also just do the SEC yourself and simply call screen_mode() directly,
|
||||
; or use the existing SCREEN kernal routine for just getting the size in characters.
|
||||
%asm {{
|
||||
@@ -555,15 +587,63 @@ asmsub mouse_config2(byte shape @A) clobbers (A, X, Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub mouse_pos() clobbers(X) -> ubyte @A {
|
||||
asmsub mouse_pos() clobbers(X) -> ubyte @A, word @R0, word @R1 {
|
||||
; -- short wrapper around mouse_get() kernal routine:
|
||||
; -- gets the position of the mouse cursor in cx16.r0 and cx16.r1 (x/y coordinate), returns mouse button status in A.
|
||||
; Note: mouse pointer needs to be enabled for this to do anything.
|
||||
%asm {{
|
||||
ldx #cx16.r0
|
||||
jmp cx16.mouse_get
|
||||
}}
|
||||
}
|
||||
|
||||
; shims for the kernal routines called via the extapi call:
|
||||
|
||||
asmsub mouse_set_pos(uword xpos @R0, uword ypos @R1) clobbers(X) {
|
||||
; -- sets the mouse sprite position
|
||||
; Note: mouse pointer needs to be enabled for this to do anything.
|
||||
%asm {{
|
||||
ldx #cx16.r0L
|
||||
lda #EXTAPI_mouse_set_position
|
||||
jmp cx16.extapi
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub mouse_set_sprite_offset(word xoffset @R0, word yoffset @R1) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
clc
|
||||
lda #EXTAPI_mouse_sprite_offset
|
||||
jmp cx16.extapi
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub mouse_get_sprite_offset() clobbers(A,X,Y) -> word @R0, word @R1 {
|
||||
%asm {{
|
||||
sec
|
||||
lda #EXTAPI_mouse_sprite_offset
|
||||
jmp cx16.extapi
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub getlfs() -> ubyte @X, ubyte @A, ubyte @Y {
|
||||
; -- return the result of the last call to SETLFS: A=logical, X=device, Y=secondary.
|
||||
%asm {{
|
||||
lda #EXTAPI_mouse_set_position
|
||||
jmp cx16.extapi
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub iso_cursor_char(ubyte character @X) clobbers(A,X,Y) {
|
||||
; -- set the screen code for the cursor character in ISO mode (the default is $9f).
|
||||
%asm {{
|
||||
clc
|
||||
lda #EXTAPI_iso_cursor_char
|
||||
jmp cx16.extapi
|
||||
}}
|
||||
}
|
||||
|
||||
; TODO : implement shims for the remaining extapi calls.
|
||||
|
||||
|
||||
; ---- end of kernal routines ----
|
||||
|
||||
@@ -918,7 +998,7 @@ asmsub restore_vera_context() clobbers(A) {
|
||||
|
||||
asmsub set_chrin_keyhandler(ubyte handlerbank @A, uword handler @XY) clobbers(A) {
|
||||
; Install a custom CHRIN (BASIN) key handler in a safe manner. Call this before each line you want to read.
|
||||
; See https://github.com/X16Community/x16-docs/blob/master/X16%20Reference%20-%2002%20-%20Editor.md#custom-basin-petscii-code-override-handler
|
||||
; See https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2003%20-%20Editor.md#custom-basin-petscii-code-override-handler
|
||||
%asm {{
|
||||
sei
|
||||
sta P8ZP_SCRATCH_REG
|
||||
@@ -1158,7 +1238,7 @@ sub search_x16edit() -> ubyte {
|
||||
}
|
||||
|
||||
asmsub cpu_is_65816() -> bool @A {
|
||||
; Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
; -- Returns true when you have a 65816 cpu, false when it's a 6502.
|
||||
%asm {{
|
||||
php
|
||||
clv
|
||||
@@ -1174,9 +1254,9 @@ sub search_x16edit() -> ubyte {
|
||||
}
|
||||
|
||||
sub set_program_args(uword args_ptr, ubyte args_size) {
|
||||
; Set the inter-program arguments.
|
||||
; -- Set the inter-program arguments.
|
||||
; standardized way to pass arguments between programs is in ram bank 0, address $bf00-$bfff.
|
||||
; see https://github.com/X16Community/x16-docs/blob/master/X16%20Reference%20-%2007%20-%20Memory%20Map.md#bank-0
|
||||
; see https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2008%20-%20Memory%20Map.md#bank-0
|
||||
sys.push(getrambank())
|
||||
rambank(0)
|
||||
sys.memcopy(args_ptr, $bf00, args_size)
|
||||
@@ -1186,9 +1266,9 @@ sub search_x16edit() -> ubyte {
|
||||
}
|
||||
|
||||
asmsub get_program_args(uword buffer @R0, ubyte buf_size @R1, bool binary @Pc) {
|
||||
; Retrieve the inter-program arguments. If binary=false, it treats them as a string (stops copying at first zero).
|
||||
; -- Retrieve the inter-program arguments. If binary=false, it treats them as a string (stops copying at first zero).
|
||||
; standardized way to pass arguments between programs is in ram bank 0, address $bf00-$bfff.
|
||||
; see https://github.com/X16Community/x16-docs/blob/master/X16%20Reference%20-%2007%20-%20Memory%20Map.md#bank-0
|
||||
; see https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2008%20-%20Memory%20Map.md#bank-0
|
||||
%asm {{
|
||||
lda #0
|
||||
rol a
|
||||
@@ -1215,17 +1295,21 @@ _continue iny
|
||||
}
|
||||
|
||||
sub reset_system() {
|
||||
; Soft-reset the system back to initial power-on Basic prompt.
|
||||
; -- Soft-reset the system back to initial power-on Basic prompt.
|
||||
sys.reset_system()
|
||||
}
|
||||
|
||||
sub poweroff_system() {
|
||||
; use the SMC to shutdown the computer
|
||||
; -- use the SMC to shutdown the computer
|
||||
void cx16.i2c_write_byte($42, $01, $00)
|
||||
}
|
||||
|
||||
sub set_led_brightness(ubyte brightness) {
|
||||
void cx16.i2c_write_byte($42, $05, brightness)
|
||||
sub set_led_state(bool on) {
|
||||
; -- sets the computer's activity led on/off
|
||||
cx16.r0L = 0
|
||||
if on
|
||||
cx16.r0 = 255
|
||||
void cx16.i2c_write_byte($42, $05, cx16.r0L)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1442,11 +1526,6 @@ asmsub set_rasterline(uword line @AY) {
|
||||
void cx16.i2c_write_byte($42, $01, $00)
|
||||
}
|
||||
|
||||
sub set_leds_brightness(ubyte activity, ubyte power) {
|
||||
void cx16.i2c_write_byte($42, $04, power)
|
||||
void cx16.i2c_write_byte($42, $05, activity)
|
||||
}
|
||||
|
||||
asmsub wait(uword jiffies @AY) clobbers(X) {
|
||||
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
|
||||
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
%import syslib
|
||||
%import conv
|
||||
%import shared_textio_functions
|
||||
%import shared_cbm_textio_functions
|
||||
|
||||
txt {
|
||||
|
||||
@@ -16,11 +16,17 @@ const ubyte DEFAULT_HEIGHT = 60
|
||||
const ubyte VERA_TEXTMATRIX_BANK = 1
|
||||
const uword VERA_TEXTMATRIX_ADDR = $b000
|
||||
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
|
||||
sub clear_screen() {
|
||||
chrout(147)
|
||||
}
|
||||
|
||||
sub cls() {
|
||||
chrout(147)
|
||||
}
|
||||
|
||||
sub home() {
|
||||
chrout(19)
|
||||
}
|
||||
@@ -223,8 +229,8 @@ sub uppercase() {
|
||||
}
|
||||
|
||||
sub iso() {
|
||||
; -- switch to iso-8859-15 character set
|
||||
cbm.CHROUT($0f)
|
||||
; This doesn't enable it completely: cx16.screen_set_charset(1, 0) ; iso charset
|
||||
}
|
||||
|
||||
sub iso_off() {
|
||||
@@ -232,6 +238,29 @@ sub iso_off() {
|
||||
cbm.CHROUT($8f)
|
||||
}
|
||||
|
||||
sub cp437() {
|
||||
; -- switch to CP-437 (ibm PC) character set
|
||||
cbm.CHROUT($0f) ; iso mode
|
||||
cx16.screen_set_charset(7, 0) ; charset
|
||||
%asm {{
|
||||
clc
|
||||
ldx #95 ; underscore
|
||||
lda #cx16.EXTAPI_iso_cursor_char
|
||||
jsr cx16.extapi
|
||||
}}
|
||||
}
|
||||
|
||||
sub iso5() {
|
||||
; -- switch to iso-8859-5 character set (Cyrillic)
|
||||
cbm.CHROUT($0f) ; iso mode
|
||||
cx16.screen_set_charset(8, 0) ; charset
|
||||
}
|
||||
|
||||
sub iso16() {
|
||||
; -- switch to iso-8859-16 character set (Eastern Europe)
|
||||
cbm.CHROUT($0f) ; iso mode
|
||||
cx16.screen_set_charset(10, 0) ; charset
|
||||
}
|
||||
|
||||
asmsub scroll_left() clobbers(A, X, Y) {
|
||||
; ---- scroll the whole screen 1 character to the left
|
||||
@@ -431,215 +460,6 @@ _nextline
|
||||
}}
|
||||
}
|
||||
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string, in PETSCII encoding, from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to cbm.CHROUT of that single char.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
sty P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_B1),y
|
||||
beq +
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
jsr conv.ubyte2decimal
|
||||
pha
|
||||
tya
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
jsr cbm.CHROUT
|
||||
txa
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
jsr conv.ubyte2decimal
|
||||
_print_byte_digits
|
||||
pha
|
||||
cpy #'0'
|
||||
beq +
|
||||
tya
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
jsr cbm.CHROUT
|
||||
bra _ones
|
||||
+ pla
|
||||
cmp #'0'
|
||||
beq _ones
|
||||
jsr cbm.CHROUT
|
||||
_ones txa
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the byte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
pha
|
||||
cmp #0
|
||||
bpl +
|
||||
lda #'-'
|
||||
jsr cbm.CHROUT
|
||||
+ pla
|
||||
jsr conv.byte2decimal
|
||||
bra print_ub._print_byte_digits
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
bcc +
|
||||
pha
|
||||
lda #'$'
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
+ jsr conv.ubyte2hex
|
||||
jsr cbm.CHROUT
|
||||
tya
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'%'
|
||||
jsr cbm.CHROUT
|
||||
+ ldy #8
|
||||
- lda #'0'
|
||||
asl P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'1'
|
||||
+ jsr cbm.CHROUT
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr print_ubbin
|
||||
pla
|
||||
clc
|
||||
bra print_ubbin
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in hexadecimal form (4 digits)
|
||||
; (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr print_ubhex
|
||||
pla
|
||||
clc
|
||||
bra print_ubhex
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq +
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq _allzero
|
||||
cmp #'0'
|
||||
bne _gotdigit
|
||||
iny
|
||||
bne -
|
||||
|
||||
_gotdigit
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
lda conv.uword2decimal.decTenThousands,y
|
||||
bne _gotdigit
|
||||
rts
|
||||
_allzero
|
||||
lda #'0'
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
|
||||
%asm {{
|
||||
cpy #0
|
||||
bpl +
|
||||
pha
|
||||
lda #'-'
|
||||
jsr cbm.CHROUT
|
||||
tya
|
||||
eor #255
|
||||
tay
|
||||
pla
|
||||
eor #255
|
||||
ina
|
||||
bne +
|
||||
iny
|
||||
+ bra print_uw
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
|
||||
; Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0 ; char counter = 0
|
||||
- jsr cbm.CHRIN
|
||||
cmp #$0d ; return (ascii 13) pressed?
|
||||
beq + ; yes, end.
|
||||
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
|
||||
iny
|
||||
bne -
|
||||
+ lda #0
|
||||
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
|
||||
rts
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A) {
|
||||
; ---- sets the character in the screen matrix at the given position
|
||||
%asm {{
|
||||
@@ -811,4 +631,35 @@ asmsub waitkey() -> ubyte @A {
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub chrout_lit(ubyte character @A) {
|
||||
; -- print the character always as a literal character, not as possible control code.
|
||||
%asm {{
|
||||
tax
|
||||
lda #128
|
||||
jsr cbm.CHROUT
|
||||
txa
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_lit(str text @ AY) clobbers(A,Y) {
|
||||
; -- print zero terminated string, from A/Y, as all literal characters (no control codes)
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W2),y
|
||||
beq +
|
||||
tax
|
||||
lda #128
|
||||
jsr cbm.CHROUT
|
||||
txa
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
; Somewhat experimental Vera FX support.
|
||||
; Docs:
|
||||
; https://github.com/X16Community/x16-docs/blob/master/X16%20Reference%20-%2010%20-%20VERA%20FX%20Reference.md
|
||||
; https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2010%20-%20VERA%20FX%20Reference.md
|
||||
; https://docs.google.com/document/d/1q34uWOiM3Be2pnaHRVgSdHySI-qsiQWPTo_gfE54PTg
|
||||
|
||||
verafx {
|
||||
@@ -111,17 +111,15 @@ verafx {
|
||||
|
||||
; unsigned multiplication just passes the values as signed to muls
|
||||
; if you do this yourself in your call to muls, it will save a few instructions.
|
||||
sub mult(uword value1, uword value2) -> uword {
|
||||
; Returns the lower 16 bits of the 32 bits result,
|
||||
; the upper 16 bits are stored in cx16.r0 so you can access those separately.
|
||||
; It's not part of the subroutine's signature to avoid awkward use of multiple returnvalues.
|
||||
return muls(value1 as word, value2 as word) as uword
|
||||
inline asmsub mult(uword value1 @R0, uword value2 @R1) clobbers(X) -> uword @AY, uword @R0 {
|
||||
; Returns the 32 bits unsigned result in AY and R0 (lower word, upper word).
|
||||
%asm {{
|
||||
jsr verafx.muls
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> word @AY {
|
||||
; Returns the lower 16 bits of the 32 bits result in AY,
|
||||
; the upper 16 bits are stored in cx16.r0 so you can access those separately.
|
||||
; It's not part of the subroutine's signature to avoid awkward use of multiple returnvalues.
|
||||
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> word @AY, word @R0 {
|
||||
; Returns the 32 bits signed result in AY and R0 (lower word, upper word).
|
||||
%asm {{
|
||||
lda #(2 << 1)
|
||||
sta cx16.VERA_CTRL ; $9F25
|
||||
@@ -131,13 +129,13 @@ verafx {
|
||||
lda #(6 << 1)
|
||||
sta cx16.VERA_CTRL ; $9F25
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
sta cx16.VERA_FX_CACHE_L ; $9F29
|
||||
sty cx16.VERA_FX_CACHE_M ; $9F2A
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_FX_CACHE_M ; $9F2A
|
||||
lda cx16.r1
|
||||
ldy cx16.r1+1
|
||||
sta cx16.VERA_FX_CACHE_H ; $9F2B
|
||||
sty cx16.VERA_FX_CACHE_U ; $9F2C
|
||||
lda cx16.r1+1
|
||||
sta cx16.VERA_FX_CACHE_U ; $9F2C
|
||||
lda cx16.VERA_FX_ACCUM_RESET ; $9F29 (DCSEL=6)
|
||||
|
||||
; Set the ADDR0 pointer to $1f9bc and write our multiplication result there
|
||||
@@ -155,12 +153,12 @@ verafx {
|
||||
stz cx16.VERA_DATA0 ; multiply and write out result
|
||||
lda #%00010001 ; $01 with Increment 1
|
||||
sta cx16.VERA_ADDR_H ; so we can read out the result
|
||||
lda cx16.VERA_DATA0
|
||||
lda cx16.VERA_DATA0 ; store the lower 16 bits of the result in AY
|
||||
ldy cx16.VERA_DATA0
|
||||
ldx cx16.VERA_DATA0 ; store the upper 16 bits of the result in r0
|
||||
stx cx16.r0
|
||||
ldx cx16.VERA_DATA0 ; store the upper 16 bits of the result in R0
|
||||
stx cx16.r0s
|
||||
ldx cx16.VERA_DATA0
|
||||
stx cx16.r0+1
|
||||
stx cx16.r0s+1
|
||||
stz cx16.VERA_FX_CTRL ; Cache write disable
|
||||
stz cx16.VERA_CTRL ; reset DCSEL
|
||||
rts
|
||||
|
||||
@@ -59,7 +59,8 @@ diskio {
|
||||
void cbm.CHRIN() ; skip 2 bytes
|
||||
void cbm.CHRIN()
|
||||
status = cbm.READST()
|
||||
if cbm.STOP2()
|
||||
void cbm.STOP()
|
||||
if_z
|
||||
break
|
||||
}
|
||||
status = cbm.READST()
|
||||
@@ -460,6 +461,38 @@ io_error:
|
||||
goto done
|
||||
}
|
||||
|
||||
; similar to above, but instead of fetching the entire string, it only fetches the status code and returns it as ubyte
|
||||
; in case of IO error, returns 255 (CBM-DOS itself is physically unable to return such a value)
|
||||
sub status_code() -> ubyte {
|
||||
if cbm.READST()==128 {
|
||||
return 255
|
||||
}
|
||||
|
||||
cbm.SETNAM(0, list_filename)
|
||||
cbm.SETLFS(15, drivenumber, 15)
|
||||
void cbm.OPEN() ; open 15,8,15
|
||||
if_cs
|
||||
goto io_error
|
||||
void cbm.CHKIN(15) ; use #15 as input channel
|
||||
|
||||
list_filename[0] = cbm.CHRIN()
|
||||
list_filename[1] = cbm.CHRIN()
|
||||
list_filename[2] = 0
|
||||
|
||||
while cbm.READST()==0 {
|
||||
void cbm.CHRIN()
|
||||
}
|
||||
|
||||
cbm.CLRCHN() ; restore default i/o devices
|
||||
cbm.CLOSE(15)
|
||||
return conv.str2ubyte(list_filename)
|
||||
|
||||
io_error:
|
||||
cbm.CLRCHN()
|
||||
cbm.CLOSE(15)
|
||||
return 255
|
||||
}
|
||||
|
||||
sub save(uword filenameptr, uword start_address, uword savesize) -> bool {
|
||||
cbm.SETNAM(string.length(filenameptr), filenameptr)
|
||||
cbm.SETLFS(1, drivenumber, 0)
|
||||
|
||||
@@ -29,20 +29,26 @@ romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; define an outp
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte character @ A) ; output a character
|
||||
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; check the STOP key (and some others in A)
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; get a character
|
||||
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; check the STOP key (and some others in A) also see STOP2
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; get a character also see GETIN2
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
|
||||
asmsub STOP2() clobbers(X) -> bool @A {
|
||||
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
|
||||
|
||||
inline asmsub STOP2() clobbers(X,A) -> bool @Pz {
|
||||
; -- just like STOP, but omits the special keys result value in A.
|
||||
; just for convenience because most of the times you're only interested in the stop pressed or not status.
|
||||
%asm {{
|
||||
jsr cbm.STOP
|
||||
beq +
|
||||
lda #0
|
||||
rts
|
||||
+ lda #1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub GETIN2() clobbers(X,Y) -> ubyte @A {
|
||||
; -- just like GETIN, but omits the carry flag result value.
|
||||
; just for convenience because GETIN is so often used to just read keyboard input,
|
||||
; where you don't have to deal with a potential error status
|
||||
%asm {{
|
||||
jsr cbm.GETIN
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
%import syslib
|
||||
%import conv
|
||||
%import shared_textio_functions
|
||||
%import shared_cbm_textio_functions
|
||||
|
||||
txt {
|
||||
%option no_symbol_prefixing, ignore_unused
|
||||
@@ -12,11 +12,17 @@ txt {
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
|
||||
sub clear_screen() {
|
||||
chrout(147)
|
||||
}
|
||||
|
||||
sub cls() {
|
||||
chrout(147)
|
||||
}
|
||||
|
||||
sub home() {
|
||||
chrout(19)
|
||||
}
|
||||
@@ -145,216 +151,6 @@ asmsub scroll_down () clobbers(A,X) {
|
||||
}}
|
||||
}
|
||||
|
||||
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string, in PETSCII encoding, from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to cbm.CHROUT of that single char.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
sty P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_B1),y
|
||||
beq +
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
jsr conv.ubyte2decimal
|
||||
pha
|
||||
tya
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
jsr cbm.CHROUT
|
||||
txa
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
jsr conv.ubyte2decimal
|
||||
_print_byte_digits
|
||||
pha
|
||||
cpy #'0'
|
||||
beq +
|
||||
tya
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
jsr cbm.CHROUT
|
||||
jmp _ones
|
||||
+ pla
|
||||
cmp #'0'
|
||||
beq _ones
|
||||
jsr cbm.CHROUT
|
||||
_ones txa
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the byte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
pha
|
||||
cmp #0
|
||||
bpl +
|
||||
lda #'-'
|
||||
jsr cbm.CHROUT
|
||||
+ pla
|
||||
jsr conv.byte2decimal
|
||||
jmp print_ub._print_byte_digits
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
bcc +
|
||||
pha
|
||||
lda #'$'
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
+ jsr conv.ubyte2hex
|
||||
jsr cbm.CHROUT
|
||||
tya
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'%'
|
||||
jsr cbm.CHROUT
|
||||
+ ldy #8
|
||||
- lda #'0'
|
||||
asl P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'1'
|
||||
+ jsr cbm.CHROUT
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr print_ubbin
|
||||
pla
|
||||
clc
|
||||
jmp print_ubbin
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in hexadecimal form (4 digits)
|
||||
; (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr print_ubhex
|
||||
pla
|
||||
clc
|
||||
jmp print_ubhex
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq +
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq _allzero
|
||||
cmp #'0'
|
||||
bne _gotdigit
|
||||
iny
|
||||
bne -
|
||||
|
||||
_gotdigit
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
lda conv.uword2decimal.decTenThousands,y
|
||||
bne _gotdigit
|
||||
rts
|
||||
_allzero
|
||||
lda #'0'
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
|
||||
%asm {{
|
||||
cpy #0
|
||||
bpl +
|
||||
pha
|
||||
lda #'-'
|
||||
jsr cbm.CHROUT
|
||||
tya
|
||||
eor #255
|
||||
tay
|
||||
pla
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
bcc +
|
||||
iny
|
||||
+ jmp print_uw
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
|
||||
; Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0 ; char counter = 0
|
||||
- jsr cbm.CHRIN
|
||||
cmp #$0d ; return (ascii 13) pressed?
|
||||
beq + ; yes, end.
|
||||
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
|
||||
iny
|
||||
bne -
|
||||
+ lda #0
|
||||
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
|
||||
rts
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y) {
|
||||
; ---- sets the character in the screen matrix at the given position
|
||||
%asm {{
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
txt {
|
||||
; the textio functions common across CBM-compatible compiler targets (c64, c128, pet32 and cx16)
|
||||
|
||||
%option merge, no_symbol_prefixing, ignore_unused
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print zero terminated string, in PETSCII encoding, from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to cbm.CHROUT of that single char.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W2),y
|
||||
beq +
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
bcc +
|
||||
pha
|
||||
lda #'$'
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
+ jsr conv.internal_ubyte2hex
|
||||
jsr cbm.CHROUT
|
||||
tya
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'%'
|
||||
jsr cbm.CHROUT
|
||||
+ ldy #8
|
||||
- lda #'0'
|
||||
asl P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'1'
|
||||
+ jsr cbm.CHROUT
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr print_ubbin
|
||||
pla
|
||||
clc
|
||||
jmp print_ubbin
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in hexadecimal form (4 digits)
|
||||
; (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr print_ubhex
|
||||
pla
|
||||
clc
|
||||
jmp print_ubhex
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
jsr conv.internal_uword2decimal
|
||||
ldy #0
|
||||
- lda conv.internal_uword2decimal.decTenThousands,y
|
||||
beq +
|
||||
jsr cbm.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
jsr conv.internal_uword2decimal
|
||||
ldy #0
|
||||
- lda conv.internal_uword2decimal.decTenThousands,y
|
||||
beq _allzero
|
||||
cmp #'0'
|
||||
bne _gotdigit
|
||||
iny
|
||||
bne -
|
||||
_gotdigit jsr cbm.CHROUT
|
||||
iny
|
||||
lda conv.internal_uword2decimal.decTenThousands,y
|
||||
bne _gotdigit
|
||||
rts
|
||||
_allzero lda #'0'
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
|
||||
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
|
||||
%asm {{
|
||||
cpy #0
|
||||
bpl +
|
||||
pha
|
||||
lda #'-'
|
||||
jsr cbm.CHROUT
|
||||
tya
|
||||
eor #255
|
||||
tay
|
||||
pla
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
bcc +
|
||||
iny
|
||||
+ jmp print_uw
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
|
||||
; Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0 ; char counter = 0
|
||||
- jsr cbm.CHRIN
|
||||
cmp #$0d ; return (ascii 13) pressed?
|
||||
beq + ; yes, end.
|
||||
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
|
||||
iny
|
||||
bne -
|
||||
+ lda #0
|
||||
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub petscii2scr(ubyte petscii_char @A) -> ubyte @A {
|
||||
; -- convert petscii character to screencode
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
tax
|
||||
lda _offsets,x
|
||||
eor P8ZP_SCRATCH_REG
|
||||
rts
|
||||
_offsets .byte 128, 0, 64, 32, 64, 192, 128, 128
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub petscii2scr_str(str petscii_string @AY) {
|
||||
; -- convert petscii string to screencodes string
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq +
|
||||
jsr petscii2scr
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
sub print_bool(bool value) {
|
||||
if value
|
||||
txt.print("true")
|
||||
else
|
||||
txt.print("false")
|
||||
}
|
||||
|
||||
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
jsr conv.internal_ubyte2decimal
|
||||
pha
|
||||
tya
|
||||
jsr cbm.CHROUT
|
||||
txa
|
||||
jsr cbm.CHROUT
|
||||
pla
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
jsr conv.internal_ubyte2decimal
|
||||
_print_byte_digits
|
||||
pha
|
||||
cpy #'0'
|
||||
beq +
|
||||
tya
|
||||
jsr cbm.CHROUT
|
||||
txa
|
||||
jsr cbm.CHROUT
|
||||
jmp _ones
|
||||
+ cpx #'0'
|
||||
beq _ones
|
||||
txa
|
||||
jsr cbm.CHROUT
|
||||
_ones pla
|
||||
jmp cbm.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
|
||||
; ---- print the byte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
pha
|
||||
cmp #0
|
||||
bpl +
|
||||
lda #'-'
|
||||
jsr cbm.CHROUT
|
||||
+ pla
|
||||
jsr conv.internal_byte2decimal
|
||||
jmp print_ub._print_byte_digits
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,11 @@ floats {
|
||||
; the floating point functions shared across compiler targets
|
||||
%option merge, no_symbol_prefixing, ignore_unused
|
||||
|
||||
const float π = 3.141592653589793
|
||||
const float PI = π
|
||||
const float TWOPI = 2*π
|
||||
|
||||
|
||||
asmsub print(float value @FAC1) clobbers(A,X,Y) {
|
||||
; ---- prints the floating point value (without a newline). No leading space (unlike BASIC)!
|
||||
%asm {{
|
||||
@@ -88,6 +93,20 @@ sub atan(float value) -> float {
|
||||
}}
|
||||
}
|
||||
|
||||
; two-argument arctangent that returns an angle in the correct quadrant
|
||||
; for the signs of x and y, normalized to the range [0, 2π]
|
||||
sub atan2(float y, float x) -> float {
|
||||
float atn = atan(y / x)
|
||||
if x < 0 atn += π
|
||||
if atn < 0 atn += 2*π
|
||||
return atn
|
||||
}
|
||||
|
||||
; reciprocal functions
|
||||
sub secant(float value) -> float { return 1.0 / cos(value) }
|
||||
sub csc(float value) -> float { return 1.0 / sin(value) }
|
||||
sub cot(float value) -> float { return 1.0 / tan(value) }
|
||||
|
||||
sub ln(float value) -> float {
|
||||
%asm {{
|
||||
lda #<value
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
txt {
|
||||
; the textio functions shared across compiler targets
|
||||
%option merge, no_symbol_prefixing, ignore_unused
|
||||
|
||||
asmsub petscii2scr(ubyte petscii_char @A) -> ubyte @A {
|
||||
; -- convert petscii character to screencode
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
tax
|
||||
lda _offsets,x
|
||||
eor P8ZP_SCRATCH_REG
|
||||
rts
|
||||
_offsets .byte 128, 0, 64, 32, 64, 192, 128, 128
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub petscii2scr_str(str petscii_string @AY) {
|
||||
; -- convert petscii string to screencodes string
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq +
|
||||
jsr petscii2scr
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
sub print_bool(bool value) {
|
||||
if value
|
||||
txt.print("true")
|
||||
else
|
||||
txt.print("false")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -145,6 +145,12 @@ diskio {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
sub status_code() -> ubyte {
|
||||
; -- return status code instead of whole CBM-DOS status string. (in this case always 255, which means 'unable to return sensible value')
|
||||
return 255
|
||||
}
|
||||
|
||||
|
||||
sub save(uword filenameptr, uword start_address, uword savesize) -> bool {
|
||||
%ir {{
|
||||
load.b r65532,0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
; Emulator debug interface. (reflecting the Commander X16 emudbg library module)
|
||||
; Docs: https://github.com/X16Community/x16-emulator#emulator-io-registers
|
||||
; Docs: https://github.com/X16Community/x16-emulator/tree/d52f118ce893fa24c4ca021a0b8de46cb80e5ccf#emulator-io-registers
|
||||
|
||||
%import textio
|
||||
|
||||
|
||||
@@ -80,6 +80,20 @@ sub atan(float value) -> float {
|
||||
}}
|
||||
}
|
||||
|
||||
; two-argument arctangent that returns an angle in the correct quadrant
|
||||
; for the signs of x and y, normalized to the range [0, 2π]
|
||||
sub atan2(float y, float x) -> float {
|
||||
float atn = atan(y / x)
|
||||
if x < 0 atn += π
|
||||
if atn < 0 atn += 2*π
|
||||
return atn
|
||||
}
|
||||
|
||||
; reciprocal functions
|
||||
sub secant(float value) -> float { return 1.0 / cos(value) }
|
||||
sub csc(float value) -> float { return 1.0 / sin(value) }
|
||||
sub cot(float value) -> float { return 1.0 / tan(value) }
|
||||
|
||||
sub ln(float value) -> float {
|
||||
%ir {{
|
||||
loadm.f fr0,floats.ln.value
|
||||
|
||||
@@ -28,6 +28,10 @@ sub clear_screen() {
|
||||
}}
|
||||
}
|
||||
|
||||
sub cls() {
|
||||
clear_screen()
|
||||
}
|
||||
|
||||
sub nl() {
|
||||
chrout('\n')
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation")
|
||||
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
|
||||
val dumpVariables by cli.option(ArgType.Boolean, fullName = "dumpvars", description = "print a dump of the variables in the program")
|
||||
val dumpSymbols by cli.option(ArgType.Boolean, fullName = "dumpsymbols", description = "print a dump of the variable declarations and subroutine signatures")
|
||||
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
|
||||
val noStrictBool by cli.option(ArgType.Boolean, fullName = "nostrictbool", description = "allow implicit conversions between bool and bytes")
|
||||
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform code optimizations")
|
||||
@@ -56,6 +57,8 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val printAst2 by cli.option(ArgType.Boolean, fullName = "printast2", description = "print out the intermediate AST that is used for code generation")
|
||||
val breakpointCpuInstruction by cli.option(ArgType.Choice(listOf("brk", "stp"), { it }), fullName = "breakinstr", description = "the CPU instruction to use as well for %breakpoint")
|
||||
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${PETTarget.NAME}', '${VMTarget.NAME}') (required)")
|
||||
val bytes2float by cli.option(ArgType.String, fullName = "bytes2float", description = "convert a comma separated list of bytes from the target system to a float value. NOTE: you need to supply a target option too, and also still have to supply a dummy module file name as well!")
|
||||
val float2bytes by cli.option(ArgType.String, fullName = "float2bytes", description = "convert floating point number to a list of bytes for the target system. NOTE: you need to supply a target option too, and also still have to supply a dummy module file name as well!")
|
||||
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "load and run a .p8ir IR source file in the VM")
|
||||
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
|
||||
val varsGolden by cli.option(ArgType.Boolean, fullName = "varsgolden", description = "put uninitialized variables in 'golden ram' memory area instead of at the end of the program. On the cx16 target this is $0400-07ff. This is unavailable on other systems.")
|
||||
@@ -99,6 +102,11 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
}
|
||||
}
|
||||
|
||||
if(bytes2float!=null)
|
||||
return convertBytesToFloat(bytes2float!!, compilationTarget!!)
|
||||
if(float2bytes!=null)
|
||||
return convertFloatToBytes(float2bytes!!, compilationTarget!!)
|
||||
|
||||
if(varsHighBank==0 && compilationTarget==Cx16Target.NAME) {
|
||||
System.err.println("On the Commander X16, HiRAM bank 0 is used by the kernal and can't be used.")
|
||||
return false
|
||||
@@ -155,6 +163,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
includeSourcelines == true,
|
||||
experimentalCodegen == true,
|
||||
dumpVariables == true,
|
||||
dumpSymbols == true,
|
||||
varsHighBank,
|
||||
varsGolden == true,
|
||||
slabsHighBank,
|
||||
@@ -235,6 +244,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
includeSourcelines == true,
|
||||
experimentalCodegen == true,
|
||||
dumpVariables == true,
|
||||
dumpSymbols==true,
|
||||
varsHighBank,
|
||||
varsGolden == true,
|
||||
slabsHighBank,
|
||||
@@ -287,6 +297,23 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
fun convertFloatToBytes(number: String, target: String): Boolean {
|
||||
val tgt = getCompilationTargetByName(target)
|
||||
val dbl = number.toDouble()
|
||||
val bytes = tgt.machine.convertFloatToBytes(dbl)
|
||||
print("$dbl in bytes on '$target': ")
|
||||
println(bytes.joinToString(","))
|
||||
return true
|
||||
}
|
||||
|
||||
fun convertBytesToFloat(bytelist: String, target: String): Boolean {
|
||||
val tgt = getCompilationTargetByName(target)
|
||||
val bytes = bytelist.split(',').map { it.trim().toUByte() }
|
||||
val number = tgt.machine.convertBytesToFloat(bytes)
|
||||
println("floating point value on '$target': $number")
|
||||
return true
|
||||
}
|
||||
|
||||
private fun processSymbolDefs(symbolDefs: List<String>): Map<String, String>? {
|
||||
val result = mutableMapOf<String, String>()
|
||||
val defPattern = """(.+)\s*=\s*(.+)""".toRegex()
|
||||
|
||||
@@ -7,13 +7,17 @@ import prog8.ast.base.AstException
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.printProgram
|
||||
import prog8.ast.printSymbols
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.code.SymbolTableMaker
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.ast.printAst
|
||||
import prog8.code.core.*
|
||||
import prog8.code.optimize.optimizeIntermediateAst
|
||||
import prog8.code.target.*
|
||||
import prog8.code.target.AtariTarget
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.code.target.getCompilationTargetByName
|
||||
import prog8.codegen.vm.VmCodeGen
|
||||
import prog8.compiler.astprocessing.*
|
||||
import prog8.optimizer.*
|
||||
@@ -22,6 +26,7 @@ import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.nameWithoutExtension
|
||||
import kotlin.math.round
|
||||
import kotlin.system.exitProcess
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
|
||||
@@ -39,6 +44,7 @@ class CompilerArguments(val filepath: Path,
|
||||
val includeSourcelines: Boolean,
|
||||
val experimentalCodegen: Boolean,
|
||||
val dumpVariables: Boolean,
|
||||
val dumpSymbols: Boolean,
|
||||
val varsHighBank: Int?,
|
||||
val varsGolden: Boolean,
|
||||
val slabsHighBank: Int?,
|
||||
@@ -57,21 +63,11 @@ class CompilerArguments(val filepath: Path,
|
||||
|
||||
fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
|
||||
val compTarget =
|
||||
when(args.compilationTarget) {
|
||||
C64Target.NAME -> C64Target()
|
||||
C128Target.NAME -> C128Target()
|
||||
Cx16Target.NAME -> Cx16Target()
|
||||
PETTarget.NAME -> PETTarget()
|
||||
AtariTarget.NAME -> AtariTarget()
|
||||
VMTarget.NAME -> VMTarget()
|
||||
else -> throw IllegalArgumentException("invalid compilation target")
|
||||
}
|
||||
|
||||
val compTarget = getCompilationTargetByName(args.compilationTarget)
|
||||
var compilationOptions: CompilationOptions
|
||||
var ast: PtProgram? = null
|
||||
var resultingProgram: Program? = null
|
||||
var importedFiles: List<Path> = emptyList()
|
||||
var importedFiles: List<Path>
|
||||
|
||||
try {
|
||||
val totalTime = measureTimeMillis {
|
||||
@@ -86,6 +82,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
includeSourcelines = args.includeSourcelines
|
||||
experimentalCodegen = args.experimentalCodegen
|
||||
dumpVariables = args.dumpVariables
|
||||
dumpSymbols = args.dumpSymbols
|
||||
breakpointCpuInstruction = args.breakpointCpuInstruction
|
||||
varsHighBank = args.varsHighBank
|
||||
varsGolden = args.varsGolden
|
||||
@@ -134,8 +131,12 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
}
|
||||
|
||||
val intermediateAst = IntermediateAstMaker(program, args.errors).transform()
|
||||
optimizeIntermediateAst(intermediateAst, compilationOptions, args.errors)
|
||||
args.errors.report()
|
||||
val stMaker = SymbolTableMaker(intermediateAst, compilationOptions)
|
||||
val symbolTable = stMaker.make()
|
||||
if(compilationOptions.optimize) {
|
||||
optimizeIntermediateAst(intermediateAst, compilationOptions, symbolTable, args.errors)
|
||||
args.errors.report()
|
||||
}
|
||||
|
||||
if(args.printAst2) {
|
||||
println("\n*********** INTERMEDIATE AST *************")
|
||||
@@ -398,6 +399,12 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
|
||||
|
||||
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
program.preprocessAst(errors, compilerOptions)
|
||||
|
||||
if(compilerOptions.dumpSymbols) {
|
||||
printSymbols(program)
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
program.checkIdentifiers(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.charLiteralsToUByteLiterals(compilerOptions.compTarget, errors)
|
||||
@@ -444,8 +451,13 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
|
||||
break
|
||||
}
|
||||
removeUnusedCode(program, errors, compilerOptions)
|
||||
if(errors.noErrors())
|
||||
if(errors.noErrors()) {
|
||||
// last round of optimizations because constFold may have enabled more...
|
||||
program.simplifyExpressions(errors, compilerOptions)
|
||||
program.optimizeStatements(errors, functions, compilerOptions)
|
||||
program.constantFold(errors, compilerOptions) // because simplified statements and expressions can result in more constants that can be folded away
|
||||
}
|
||||
|
||||
errors.report()
|
||||
}
|
||||
|
||||
@@ -477,8 +489,10 @@ private fun createAssemblyAndAssemble(program: PtProgram,
|
||||
else
|
||||
throw NotImplementedError("no code generator for cpu ${compilerOptions.compTarget.machine.cpu}")
|
||||
|
||||
// need to make a new symboltable here to capture possible changes made by optimization steps performed earlier!
|
||||
val stMaker = SymbolTableMaker(program, compilerOptions)
|
||||
val symbolTable = stMaker.make()
|
||||
|
||||
val assembly = asmgen.generate(program, symbolTable, compilerOptions, errors)
|
||||
errors.report()
|
||||
|
||||
|
||||
@@ -52,6 +52,8 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
override fun visit(module: Module) {
|
||||
super.visit(module)
|
||||
if(module.name.startsWith('_'))
|
||||
errors.err("identifiers cannot start with an underscore", module.position)
|
||||
val directives = module.statements.filterIsInstance<Directive>().groupBy { it.directive }
|
||||
directives.filter { it.value.size > 1 }.forEach{ entry ->
|
||||
when(entry.key) {
|
||||
@@ -62,6 +64,18 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(identifier: IdentifierReference) {
|
||||
if(identifier.nameInSource.any { it.startsWith('_') }) {
|
||||
errors.err("identifiers cannot start with an underscore", identifier.position)
|
||||
}
|
||||
if(identifier.nameInSource.any { it=="void" }) {
|
||||
// 'void' as "identifier" is only allowed as part of a multi-assignment expression
|
||||
if (!(identifier.nameInSource == listOf("void") && (identifier.parent as? AssignTarget)?.multi?.isNotEmpty() == true
|
||||
|| identifier.parent is AssignTarget && (identifier.parent.parent as? AssignTarget)?.multi?.isNotEmpty() == true)
|
||||
) {
|
||||
errors.err("identifiers cannot contain the 'void' keyword", identifier.position)
|
||||
}
|
||||
}
|
||||
|
||||
checkLongType(identifier)
|
||||
val stmt = identifier.targetStatement(program)
|
||||
if(stmt==null)
|
||||
@@ -116,9 +130,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
if(expectedReturnValues.size==1 && returnStmt.value!=null) {
|
||||
val valueDt = returnStmt.value!!.inferType(program)
|
||||
if(!valueDt.isKnown) {
|
||||
errors.err("return value type mismatch or unknown symbol", returnStmt.value!!.position)
|
||||
} else {
|
||||
if(valueDt.isKnown) {
|
||||
if (expectedReturnValues[0] != valueDt.getOr(DataType.UNDEFINED)) {
|
||||
if(valueDt istype DataType.BOOL && expectedReturnValues[0] == DataType.UBYTE) {
|
||||
// if the return value is a bool and the return type is ubyte, allow this. But give a warning.
|
||||
@@ -266,6 +278,9 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(block: Block) {
|
||||
if(block.name.startsWith('_'))
|
||||
errors.err("identifiers cannot start with an underscore", block.position)
|
||||
|
||||
val addr = block.address
|
||||
if(addr!=null && addr>65535u) {
|
||||
errors.err("block memory address must be valid integer 0..\$ffff", block.position)
|
||||
@@ -295,6 +310,9 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(label: Label) {
|
||||
if(label.name.startsWith('_'))
|
||||
errors.err("identifiers cannot start with an underscore", label.position)
|
||||
|
||||
// scope check
|
||||
if(label.parent !is Block && label.parent !is Subroutine && label.parent !is AnonymousScope) {
|
||||
errors.err("Labels can only be defined in the scope of a block, a loop body, or within another subroutine", label.position)
|
||||
@@ -336,6 +354,9 @@ internal class AstChecker(private val program: Program,
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
fun err(msg: String) = errors.err(msg, subroutine.position)
|
||||
|
||||
if(subroutine.name.startsWith('_'))
|
||||
errors.err("identifiers cannot start with an underscore", subroutine.position)
|
||||
|
||||
if(subroutine.name in BuiltinFunctions)
|
||||
err("cannot redefine a built-in function")
|
||||
|
||||
@@ -474,6 +495,9 @@ internal class AstChecker(private val program: Program,
|
||||
// Non-string and non-ubytearray Pass-by-reference datatypes can not occur as parameters to a subroutine directly
|
||||
// Instead, their reference (address) should be passed (as an UWORD).
|
||||
for(p in subroutine.parameters) {
|
||||
if(p.name.startsWith('_'))
|
||||
errors.err("identifiers cannot start with an underscore", p.position)
|
||||
|
||||
if(p.type in PassByReferenceDatatypes && p.type !in listOf(DataType.STR, DataType.ARRAY_UB)) {
|
||||
errors.err("this pass-by-reference type can't be used as a parameter type. Instead, use an uword to receive the address, or access the variable from the outer scope directly.", p.position)
|
||||
}
|
||||
@@ -517,32 +541,71 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment) {
|
||||
val targetDt = assignment.target.inferType(program)
|
||||
val valueDt = assignment.value.inferType(program)
|
||||
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
|
||||
if(targetDt.isIterable)
|
||||
errors.err("cannot assign value to string or array", assignment.value.position)
|
||||
else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD)) {
|
||||
if(targetDt.isUnknown) {
|
||||
if(assignment.target.identifier?.targetStatement(program)!=null)
|
||||
errors.err("target datatype is unknown", assignment.target.position)
|
||||
// otherwise, another error about missing symbol is already reported.
|
||||
fun checkType(target: AssignTarget, value: Expression, augmentable: Boolean) {
|
||||
val targetDt = target.inferType(program)
|
||||
val valueDt = value.inferType(program)
|
||||
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
|
||||
if(targetDt.isIterable)
|
||||
errors.err("cannot assign value to string or array", value.position)
|
||||
else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD)) {
|
||||
if(targetDt.isUnknown) {
|
||||
if(target.identifier?.targetStatement(program)!=null)
|
||||
errors.err("target datatype is unknown", target.position)
|
||||
// otherwise, another error about missing symbol is already reported.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(value is TypecastExpression) {
|
||||
if(augmentable && targetDt istype DataType.FLOAT)
|
||||
errors.err("typecasting a float value in-place makes no sense", value.position)
|
||||
}
|
||||
|
||||
val numvalue = value.constValue(program)
|
||||
if(numvalue!=null && targetDt.isKnown)
|
||||
checkValueTypeAndRange(targetDt.getOr(DataType.UNDEFINED), numvalue)
|
||||
}
|
||||
|
||||
if(assignment.target.multi==null) {
|
||||
checkType(assignment.target, assignment.value, assignment.isAugmentable)
|
||||
}
|
||||
|
||||
|
||||
val fcall = assignment.value as? IFunctionCall
|
||||
val fcallTarget = fcall?.target?.targetSubroutine(program)
|
||||
if(assignment.target.multi!=null) {
|
||||
checkMultiAssignment(assignment, fcall, fcallTarget)
|
||||
} else if(fcallTarget!=null) {
|
||||
if(fcallTarget.returntypes.size!=1) {
|
||||
errors.err("number of assignment targets doesn't match number of return values from the subroutine", fcall.position)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if(assignment.value is TypecastExpression) {
|
||||
if(assignment.isAugmentable && targetDt istype DataType.FLOAT)
|
||||
errors.err("typecasting a float value in-place makes no sense", assignment.value.position)
|
||||
}
|
||||
|
||||
val numvalue = assignment.value.constValue(program)
|
||||
if(numvalue!=null && targetDt.isKnown)
|
||||
checkValueTypeAndRange(targetDt.getOr(DataType.UNDEFINED), numvalue)
|
||||
|
||||
super.visit(assignment)
|
||||
}
|
||||
|
||||
private fun checkMultiAssignment(assignment: Assignment, fcall: IFunctionCall?, fcallTarget: Subroutine?) {
|
||||
// multi-assign: check the number of assign targets vs. the number of return values of the subroutine
|
||||
// also check the types of the variables vs the types of each return value
|
||||
if(fcall==null || fcallTarget==null) {
|
||||
errors.err("expected a function call with multiple return values", assignment.value.position)
|
||||
return
|
||||
}
|
||||
val targets = assignment.target.multi!!
|
||||
if(fcallTarget.returntypes.size!=targets.size) {
|
||||
errors.err("number of assignment targets doesn't match number of return values from the subroutine", fcall.position)
|
||||
return
|
||||
}
|
||||
fcallTarget.returntypes.zip(targets).withIndex().forEach { (index, p) ->
|
||||
val (returnType, target) = p
|
||||
val targetDt = target.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if (!target.void && !(returnType isAssignableTo targetDt))
|
||||
errors.err("can't assign returnvalue #${index + 1} to corresponding target; $returnType vs $targetDt", target.position)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun visit(assignTarget: AssignTarget) {
|
||||
super.visit(assignTarget)
|
||||
|
||||
@@ -552,6 +615,9 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("address out of range", assignTarget.position)
|
||||
}
|
||||
|
||||
if(assignTarget.parent is AssignTarget)
|
||||
return // sub-target of a multi-assign is tested elsewhere
|
||||
|
||||
val assignment = assignTarget.parent as Statement
|
||||
val targetIdentifier = assignTarget.identifier
|
||||
if (targetIdentifier != null) {
|
||||
@@ -1174,25 +1240,6 @@ internal class AstChecker(private val program: Program,
|
||||
if(error!=null)
|
||||
errors.err(error.first, error.second)
|
||||
|
||||
// check the functions that return multiple returnvalues.
|
||||
val stmt = functionCallExpr.target.targetStatement(program)
|
||||
if (stmt is Subroutine) {
|
||||
if (stmt.returntypes.size > 1) {
|
||||
// Currently, it's only possible to handle ONE (or zero) return values from a subroutine.
|
||||
// asmsub routines can have multiple return values, for instance in 2 different registers.
|
||||
// It's not (yet) possible to handle these multiple return values because assignments
|
||||
// are only to a single unique target at the same time.
|
||||
// EXCEPTION:
|
||||
// if the asmsub returns multiple values and one of them is via a status register bit (such as carry),
|
||||
// it *is* possible to handle them by just actually assigning the register value and
|
||||
// dealing with the status bit as just being that, the status bit after the call.
|
||||
val (returnRegisters, _) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
|
||||
if (returnRegisters.size>1) {
|
||||
errors.err("It's not possible to store the multiple result values of this asmsub call; you should use a small block of custom inline assembly for this.", functionCallExpr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// functions that don't return a value, can't be used in an expression or assignment
|
||||
if(targetStatement is Subroutine) {
|
||||
if(targetStatement.returntypes.isEmpty()) {
|
||||
@@ -1264,7 +1311,14 @@ internal class AstChecker(private val program: Program,
|
||||
if(funcName[0]=="setlsb" || funcName[0]=="setmsb") {
|
||||
val firstArg = functionCallStatement.args[0]
|
||||
if(firstArg !is IdentifierReference && firstArg !is ArrayIndexedExpression)
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
errors.err("invalid argument to a in-place modifying function", firstArg.position)
|
||||
} else if(funcName[0]=="divmod" || funcName[0].startsWith("divmod__")) {
|
||||
val thirdArg = functionCallStatement.args[2]
|
||||
val fourthArg = functionCallStatement.args[3]
|
||||
if(thirdArg !is IdentifierReference && thirdArg !is ArrayIndexedExpression)
|
||||
errors.err("invalid argument to a in-place modifying function", thirdArg.position)
|
||||
if(fourthArg !is IdentifierReference && fourthArg !is ArrayIndexedExpression)
|
||||
errors.err("invalid argument to a in-place modifying function", fourthArg.position)
|
||||
} else {
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead })
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
|
||||
@@ -121,7 +121,7 @@ class AstPreprocessor(val program: Program,
|
||||
// we need to handle multi-decl here too, the desugarer maybe has not processed it here yet...
|
||||
if(decl.value!=null) {
|
||||
decl.names.forEach { name ->
|
||||
val target = AssignTarget(IdentifierReference(listOf(name), decl.position), null, null, decl.position)
|
||||
val target = AssignTarget(IdentifierReference(listOf(name), decl.position), null, null, null, false, decl.position)
|
||||
val assign = Assignment(target.copy(), decl.value!!.copy(), AssignmentOrigin.VARINIT, decl.position)
|
||||
replacements.add(IAstModification.InsertAfter(decl, assign, scope))
|
||||
}
|
||||
@@ -137,7 +137,7 @@ class AstPreprocessor(val program: Program,
|
||||
} else {
|
||||
// handle declaration of a single variable
|
||||
if(decl.value!=null && (decl.datatype in NumericDatatypes || decl.datatype==DataType.BOOL)) {
|
||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, null, false, decl.position)
|
||||
val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position)
|
||||
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
|
||||
decl.value = null
|
||||
|
||||
@@ -183,7 +183,7 @@ _after:
|
||||
}
|
||||
if(functionCall.target.nameInSource==listOf("poke")) {
|
||||
// poke(a, v) is synonymous with @(a) = v
|
||||
val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), position)
|
||||
val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), null, false, position)
|
||||
val assign = Assignment(tgt, functionCall.args[1], AssignmentOrigin.OPTIMIZER, position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent))
|
||||
}
|
||||
@@ -222,7 +222,7 @@ _after:
|
||||
return if(parent is AssignTarget) {
|
||||
// assignment to array
|
||||
val memwrite = DirectMemoryWrite(address, arrayIndexedExpression.position)
|
||||
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
|
||||
val newtarget = AssignTarget(null, null, memwrite, null, false, arrayIndexedExpression.position)
|
||||
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
|
||||
} else {
|
||||
// read from array
|
||||
|
||||
@@ -90,6 +90,7 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
|
||||
|
||||
private fun transform(srcAssign: Assignment): PtNode {
|
||||
if(srcAssign.isAugmentable) {
|
||||
require(srcAssign.target.multi==null)
|
||||
val srcExpr = srcAssign.value
|
||||
val (operator: String, augmentedValue: Expression?) = when(srcExpr) {
|
||||
is BinaryExpression -> {
|
||||
@@ -136,20 +137,25 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
|
||||
}
|
||||
|
||||
val assign = PtAssignment(srcAssign.position)
|
||||
assign.add(transform(srcAssign.target))
|
||||
val multi = srcAssign.target.multi
|
||||
if(multi==null) {
|
||||
assign.add(transform(srcAssign.target))
|
||||
} else {
|
||||
multi.forEach { target -> assign.add(transform(target)) }
|
||||
}
|
||||
assign.add(transformExpression(srcAssign.value))
|
||||
return assign
|
||||
}
|
||||
|
||||
private fun transform(srcTarget: AssignTarget): PtAssignTarget {
|
||||
val target = PtAssignTarget(srcTarget.position)
|
||||
val target = PtAssignTarget(srcTarget.void, srcTarget.position)
|
||||
if(srcTarget.identifier!=null)
|
||||
target.add(transform(srcTarget.identifier!!))
|
||||
else if(srcTarget.arrayindexed!=null)
|
||||
target.add(transform(srcTarget.arrayindexed!!))
|
||||
else if(srcTarget.memoryAddress!=null)
|
||||
target.add(transform(srcTarget.memoryAddress!!))
|
||||
else
|
||||
else if(!srcTarget.void)
|
||||
throw FatalAstException("invalid AssignTarget")
|
||||
return target
|
||||
}
|
||||
@@ -264,7 +270,7 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
|
||||
|
||||
private fun transform(srcCall: FunctionCallStatement): PtFunctionCall {
|
||||
val (target, type) = srcCall.target.targetNameAndType(program)
|
||||
val call = PtFunctionCall(target,true, type, srcCall.position)
|
||||
val call = PtFunctionCall(target, true, type, srcCall.position)
|
||||
for (arg in srcCall.args)
|
||||
call.add(transformExpression(arg))
|
||||
return call
|
||||
@@ -272,11 +278,8 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
|
||||
|
||||
private fun transform(srcCall: FunctionCallExpression): PtFunctionCall {
|
||||
val (target, _) = srcCall.target.targetNameAndType(program)
|
||||
val type = srcCall.inferType(program).getOrElse {
|
||||
throw FatalAstException("unknown dt $srcCall")
|
||||
}
|
||||
val isVoid = type==DataType.UNDEFINED
|
||||
val call = PtFunctionCall(target, isVoid, type, srcCall.position)
|
||||
val iType = srcCall.inferType(program)
|
||||
val call = PtFunctionCall(target, iType.isUnknown && srcCall.parent !is Assignment, iType.getOrElse { DataType.UNDEFINED }, srcCall.position)
|
||||
for (arg in srcCall.args)
|
||||
call.add(transformExpression(arg))
|
||||
return call
|
||||
|
||||
@@ -74,6 +74,13 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.names.size>1) {
|
||||
|
||||
val fcallTarget = (decl.value as? IFunctionCall)?.target?.targetSubroutine(program)
|
||||
if(fcallTarget!=null && fcallTarget.returntypes.size>1) {
|
||||
errors.err("ambiguous multi-variable initialization. Use separate variable declaration and assignment(s) instead.", decl.value!!.position)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
// note: the desugaring of a multi-variable vardecl has to be done here
|
||||
// and not in CodeDesugarer, that one is too late (identifiers can't be found otherwise)
|
||||
if(decl.datatype !in NumericDatatypesWithBoolean)
|
||||
|
||||
@@ -60,7 +60,8 @@ internal class StatementReorderer(
|
||||
// Add assignment to initialize with zero
|
||||
// Note: for block-level vars, this will introduce assignments in the block scope. These have to be dealt with correctly later.
|
||||
val identifier = IdentifierReference(listOf(decl.name), decl.position)
|
||||
val assignzero = Assignment(AssignTarget(identifier, null, null, decl.position), decl.zeroElementValue(), AssignmentOrigin.VARINIT, decl.position)
|
||||
val assignzero = Assignment(AssignTarget(identifier, null, null, null, false, decl.position),
|
||||
decl.zeroElementValue(), AssignmentOrigin.VARINIT, decl.position)
|
||||
return listOf(IAstModification.InsertAfter(
|
||||
decl, assignzero, parent as IStatementContainer
|
||||
))
|
||||
@@ -72,7 +73,8 @@ internal class StatementReorderer(
|
||||
// So basically consider 'ubyte xx=99' as a short form for 'ubyte xx; xx=99'
|
||||
val pos = decl.value!!.position
|
||||
val identifier = IdentifierReference(listOf(decl.name), pos)
|
||||
val assign = Assignment(AssignTarget(identifier, null, null, pos), decl.value!!, AssignmentOrigin.VARINIT, pos)
|
||||
val assign = Assignment(AssignTarget(identifier, null, null, null, false, pos),
|
||||
decl.value!!, AssignmentOrigin.VARINIT, pos)
|
||||
decl.value = null
|
||||
return listOf(IAstModification.InsertAfter(
|
||||
decl, assign, parent as IStatementContainer
|
||||
@@ -90,7 +92,8 @@ internal class StatementReorderer(
|
||||
if(target!=null && target.isArray) {
|
||||
val pos = decl.value!!.position
|
||||
val identifier = IdentifierReference(listOf(decl.name), pos)
|
||||
val assign = Assignment(AssignTarget(identifier, null, null, pos), decl.value!!, AssignmentOrigin.VARINIT, pos)
|
||||
val assign = Assignment(AssignTarget(identifier, null, null, null, false, pos),
|
||||
decl.value!!, AssignmentOrigin.VARINIT, pos)
|
||||
decl.value = null
|
||||
return listOf(IAstModification.InsertAfter(
|
||||
decl, assign, parent as IStatementContainer
|
||||
|
||||
@@ -463,14 +463,16 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
|
||||
|
||||
override fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> {
|
||||
val conditionDt = (whenChoice.parent as When).condition.inferType(program)
|
||||
val values = whenChoice.values
|
||||
values?.toTypedArray()?.withIndex()?.forEach { (index, value) ->
|
||||
val valueDt = value.inferType(program)
|
||||
if(valueDt!=conditionDt) {
|
||||
val castedValue = value.typecastTo(conditionDt.getOr(DataType.UNDEFINED), valueDt.getOr(DataType.UNDEFINED), true)
|
||||
if(castedValue.first) {
|
||||
castedValue.second.linkParents(whenChoice)
|
||||
values[index] = castedValue.second
|
||||
if(conditionDt.isKnown) {
|
||||
val values = whenChoice.values
|
||||
values?.toTypedArray()?.withIndex()?.forEach { (index, value) ->
|
||||
val valueDt = value.inferType(program)
|
||||
if(valueDt!=conditionDt) {
|
||||
val castedValue = value.typecastTo(conditionDt.getOr(DataType.UNDEFINED), valueDt.getOr(DataType.UNDEFINED), true)
|
||||
if(castedValue.first) {
|
||||
castedValue.second.linkParents(whenChoice)
|
||||
values[index] = castedValue.second
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,28 +156,6 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: is this really no longer needed in boolean branch?
|
||||
if(expr.operator in LogicalOperators) {
|
||||
// remove redundant !=0 comparisons from logical expressions such as: a!=0 xor b --> a xor b (only for byte operands!)
|
||||
val leftExpr = expr.left as? BinaryExpression
|
||||
if(leftExpr != null &&
|
||||
leftExpr.operator == "!=" &&
|
||||
!leftExpr.left.isSimple &&
|
||||
leftExpr.left.inferType(program).isBytes &&
|
||||
leftExpr.right.constValue(program)?.number == 0.0) {
|
||||
return listOf(IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr))
|
||||
}
|
||||
val rightExpr = expr.right as? BinaryExpression
|
||||
if(rightExpr != null &&
|
||||
rightExpr.operator == "!=" &&
|
||||
!rightExpr.left.isSimple &&
|
||||
rightExpr.left.inferType(program).isBytes &&
|
||||
rightExpr.right.constValue(program)?.number == 0.0) {
|
||||
return listOf(IAstModification.ReplaceNode(rightExpr, rightExpr.left, expr))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
@@ -114,12 +114,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
|
||||
}
|
||||
if(target.isAsmSubroutine) {
|
||||
if(target.asmReturnvaluesRegisters.size>1) {
|
||||
// multiple return values will NOT work inside an expression.
|
||||
// they MIGHT work in a regular assignment or just a function call statement.
|
||||
// EXCEPTION:
|
||||
// if the asmsub returns multiple values and one of them is via a status register bit (such as carry),
|
||||
// it *is* possible to handle them by just actually assigning the register value and
|
||||
// dealing with the status bit as just being that, the status bit after the call.
|
||||
// multiple return values will NOT work inside an expression. Use an assignment first.
|
||||
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
|
||||
if (call !is FunctionCallStatement) {
|
||||
val checkParent =
|
||||
@@ -128,8 +123,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
|
||||
else
|
||||
parent
|
||||
if (checkParent !is Assignment && checkParent !is VarDecl) {
|
||||
val (returnRegisters, _) = target.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
|
||||
if (returnRegisters.size>1) {
|
||||
if (target.asmReturnvaluesRegisters.size>1) {
|
||||
return Pair("can't use subroutine call that returns multiple return values here", call.position)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package prog8tests
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
@@ -98,5 +99,19 @@ main {
|
||||
(a4.value as NumericLiteral).number shouldBe 200*256+100
|
||||
(a5.args[0] as NumericLiteral).number shouldBe 6.0
|
||||
}
|
||||
|
||||
test("divmod target args should be treated as variables that are written") {
|
||||
val src="""
|
||||
main {
|
||||
ubyte c
|
||||
ubyte l
|
||||
|
||||
sub start() {
|
||||
divmod(99, 10, c, l)
|
||||
}
|
||||
}"""
|
||||
|
||||
compileText(Cx16Target(), true, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
})
|
||||
|
||||
@@ -33,6 +33,7 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
|
||||
includeSourcelines = false,
|
||||
experimentalCodegen = false,
|
||||
dumpVariables = false,
|
||||
dumpSymbols = false,
|
||||
varsHighBank = null,
|
||||
varsGolden = false,
|
||||
slabsHighBank = null,
|
||||
@@ -107,6 +108,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
"vtui/testvtui",
|
||||
"pcmaudio/play-adpcm",
|
||||
"pcmaudio/stream-wav",
|
||||
"pcmaudio/vumeter",
|
||||
"sprites/dragon",
|
||||
"sprites/dragons",
|
||||
"amiga",
|
||||
@@ -115,6 +117,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
"bdmusic",
|
||||
"bobs",
|
||||
"bubbleuniverse",
|
||||
"charsets",
|
||||
"circles",
|
||||
"cobramk3-gfx",
|
||||
"colorbars",
|
||||
|
||||
@@ -31,6 +31,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
|
||||
includeSourcelines = false,
|
||||
experimentalCodegen = false,
|
||||
dumpVariables = false,
|
||||
dumpSymbols = false,
|
||||
varsHighBank = null,
|
||||
varsGolden = false,
|
||||
slabsHighBank = null,
|
||||
|
||||
@@ -42,7 +42,7 @@ main {
|
||||
"textio",
|
||||
"syslib",
|
||||
"conv",
|
||||
"shared_textio_functions",
|
||||
"shared_cbm_textio_functions",
|
||||
"floats",
|
||||
"shared_floats_functions",
|
||||
"math",
|
||||
@@ -101,7 +101,7 @@ main {
|
||||
listOf(
|
||||
internedStringsModuleName,
|
||||
filenameBase,
|
||||
"textio", "syslib", "conv", "shared_textio_functions", "floats", "shared_floats_functions", "math", "prog8_lib"
|
||||
"textio", "syslib", "conv", "shared_cbm_textio_functions", "floats", "shared_floats_functions", "math", "prog8_lib"
|
||||
)
|
||||
}
|
||||
options.floats shouldBe true
|
||||
|
||||
+21
-21
@@ -36,49 +36,49 @@ class TestMemory: FunSpec({
|
||||
test("assignment target not in mapped IO space C64") {
|
||||
|
||||
var memexpr = NumericLiteral.optimalInteger(0x0002, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, false, Position.DUMMY)
|
||||
var assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(c64target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteral.optimalInteger(0x1000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, false, Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(c64target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteral.optimalInteger(0x9fff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, false, Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(c64target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteral.optimalInteger(0xa000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, false, Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(c64target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteral.optimalInteger(0xc000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, false, Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(c64target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteral.optimalInteger(0xcfff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, false, Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(c64target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteral.optimalInteger(0xeeee, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, false, Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(c64target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteral.optimalInteger(0xffff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, false, Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(c64target.machine) shouldBe false
|
||||
@@ -87,25 +87,25 @@ class TestMemory: FunSpec({
|
||||
test("assign target in mapped IO space C64") {
|
||||
|
||||
var memexpr = NumericLiteral.optimalInteger(0x0000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, false, Position.DUMMY)
|
||||
var assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(c64target.machine) shouldBe true
|
||||
|
||||
memexpr = NumericLiteral.optimalInteger(0x0001, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, false, Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(c64target.machine) shouldBe true
|
||||
|
||||
memexpr = NumericLiteral.optimalInteger(0xd000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, false, Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(c64target.machine) shouldBe true
|
||||
|
||||
memexpr = NumericLiteral.optimalInteger(0xdfff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, false, Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(c64target.machine) shouldBe true
|
||||
@@ -114,7 +114,7 @@ class TestMemory: FunSpec({
|
||||
fun createTestProgramForMemoryRefViaVar(address: UInt, vartype: VarDeclType): AssignTarget {
|
||||
val decl = VarDecl(vartype, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, false, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(decl, assignment))
|
||||
return target
|
||||
@@ -137,13 +137,13 @@ class TestMemory: FunSpec({
|
||||
|
||||
test("memory expression mapped to IO memory on C64") {
|
||||
var memexpr = PrefixExpression("+", NumericLiteral.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, false, Position.DUMMY)
|
||||
var assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(c64target.machine) shouldBe false
|
||||
|
||||
memexpr = PrefixExpression("+", NumericLiteral.optimalInteger(0xd020, Position.DUMMY), Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), null, false, Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(c64target.machine) shouldBe true
|
||||
@@ -151,7 +151,7 @@ class TestMemory: FunSpec({
|
||||
|
||||
test("regular variable not in mapped IO ram on C64") {
|
||||
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", emptyList(), null, false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, null, false, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), mutableListOf(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
@@ -163,7 +163,7 @@ class TestMemory: FunSpec({
|
||||
test("memory mapped variable not in mapped IO ram on C64") {
|
||||
val address = 0x1000u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, null, false, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), mutableListOf(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
@@ -175,7 +175,7 @@ class TestMemory: FunSpec({
|
||||
test("memory mapped variable in mapped IO ram on C64") {
|
||||
val address = 0xd020u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, null, false, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), mutableListOf(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
@@ -187,7 +187,7 @@ class TestMemory: FunSpec({
|
||||
test("array not in mapped IO ram") {
|
||||
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", emptyList(), null, false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, null, false, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), mutableListOf(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
@@ -200,7 +200,7 @@ class TestMemory: FunSpec({
|
||||
val address = 0x1000u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, null, false, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), mutableListOf(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
@@ -213,7 +213,7 @@ class TestMemory: FunSpec({
|
||||
val address = 0xd800u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, null, false, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), mutableListOf(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
|
||||
@@ -12,6 +12,9 @@ import prog8.ast.ParentSentinel
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.ast.PtAssignTarget
|
||||
import prog8.code.ast.PtAssignment
|
||||
import prog8.code.ast.PtFunctionCall
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.target.C64Target
|
||||
@@ -790,6 +793,18 @@ main {
|
||||
(expr.right as? NumericLiteral)?.number shouldBe 10.0
|
||||
}
|
||||
|
||||
test("var to const inside typecast") {
|
||||
val src="""
|
||||
main {
|
||||
uword variable ; will be made a const
|
||||
|
||||
sub start() {
|
||||
cx16.r0 = msb(variable) as uword
|
||||
}
|
||||
}"""
|
||||
compileText(VMTarget(), true, src, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
|
||||
test("De Morgan's laws") {
|
||||
val src="""
|
||||
main {
|
||||
@@ -972,4 +987,76 @@ main {
|
||||
(right2.left as FunctionCallExpression).target.nameInSource shouldBe listOf("diskio", "f_read")
|
||||
(right3.left as FunctionCallExpression).target.nameInSource shouldBe listOf("diskio", "f_read")
|
||||
}
|
||||
|
||||
test("eliminate same target register assignments") {
|
||||
val src="""
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
|
||||
main {
|
||||
romsub ${'$'}2000 = func1() clobbers(X) -> ubyte @A, word @R0, byte @R1
|
||||
romsub ${'$'}3000 = func2() clobbers(X) -> ubyte @A, uword @R0, uword @R1
|
||||
romsub ${'$'}4000 = func3() clobbers(X) -> ubyte @R0
|
||||
|
||||
sub start() {
|
||||
bool flag
|
||||
void cbm.GETIN()
|
||||
flag, cx16.r1L = cbm.GETIN()
|
||||
void, cx16.r0s, cx16.r1sL = func1()
|
||||
void, cx16.r2, cx16.r1 = func2()
|
||||
cx16.r0L = func3()
|
||||
cx16.r0H = func3()
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), true, src, writeAssembly = true)!!
|
||||
val st = result.codegenAst!!.entrypoint()!!.children
|
||||
st.size shouldBe 9
|
||||
(st[2] as PtFunctionCall).name shouldBe "cbm.GETIN"
|
||||
(st[2] as PtFunctionCall).void shouldBe true
|
||||
val a1 = st[3] as PtAssignment
|
||||
(a1.value as PtFunctionCall).name shouldBe "cbm.GETIN"
|
||||
a1.multiTarget shouldBe true
|
||||
a1.children.size shouldBe 3
|
||||
(a1.children[0] as PtAssignTarget).void shouldBe false
|
||||
(a1.children[0] as PtAssignTarget).identifier!!.name shouldBe "p8b_main.p8s_start.p8v_flag"
|
||||
(a1.children[1] as PtAssignTarget).void shouldBe false
|
||||
(a1.children[1] as PtAssignTarget).identifier!!.name shouldBe "cx16.r1L"
|
||||
|
||||
(st[4] as PtFunctionCall).name shouldBe "p8b_main.p8s_func1"
|
||||
(st[4] as PtFunctionCall).void shouldBe true
|
||||
val a2 = st[5] as PtAssignment
|
||||
(a2.value as PtFunctionCall).name shouldBe "p8b_main.p8s_func2"
|
||||
a2.multiTarget shouldBe true
|
||||
a2.children.size shouldBe 4
|
||||
(a2.children[0] as PtAssignTarget).void shouldBe true
|
||||
(a2.children[1] as PtAssignTarget).void shouldBe false
|
||||
(a2.children[1] as PtAssignTarget).identifier!!.name shouldBe "cx16.r2"
|
||||
(a2.children[2] as PtAssignTarget).void shouldBe true
|
||||
|
||||
(st[6] as PtFunctionCall).name shouldBe "p8b_main.p8s_func3"
|
||||
(st[6] as PtFunctionCall).void shouldBe true
|
||||
val a3 = st[7] as PtAssignment
|
||||
(a3.value as PtFunctionCall).name shouldBe "p8b_main.p8s_func3"
|
||||
a3.multiTarget shouldBe false
|
||||
a3.children.size shouldBe 2
|
||||
(a3.children[0] as PtAssignTarget).void shouldBe false
|
||||
(a3.children[0] as PtAssignTarget).identifier!!.name shouldBe "cx16.r0H"
|
||||
}
|
||||
|
||||
test("symbol table correct in asmgen after earlier optimization steps") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
uword @shared bulletRef, enemyRef
|
||||
const ubyte BD_Y = 10
|
||||
const ubyte EN_Y = 11
|
||||
|
||||
if bulletRef[BD_Y] == enemyRef[EN_Y] or bulletRef[BD_Y] == enemyRef[EN_Y] + 1 {
|
||||
cx16.r0++
|
||||
}
|
||||
}
|
||||
}"""
|
||||
compileText(VMTarget(), true, src, writeAssembly = true) shouldNotBe null
|
||||
compileText(C64Target(), true, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
})
|
||||
@@ -16,9 +16,9 @@ import prog8.code.core.unescape
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.Encoder
|
||||
import prog8.code.target.cbm.AtasciiEncoding
|
||||
import prog8.code.target.cbm.IsoEncoding
|
||||
import prog8.code.target.cbm.PetsciiEncoding
|
||||
import prog8.code.target.encodings.AtasciiEncoding
|
||||
import prog8.code.target.encodings.IsoEncoding
|
||||
import prog8.code.target.encodings.PetsciiEncoding
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.printProgram
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.IfElse
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.code.ast.PtAsmSub
|
||||
import prog8.code.ast.PtSub
|
||||
import prog8.code.core.DataType
|
||||
@@ -826,4 +825,18 @@ main {
|
||||
}"""
|
||||
compileText(C64Target(), true, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("bool to word cast") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
bool @shared flag, flag2
|
||||
cx16.r0L = (flag and flag2) as ubyte
|
||||
cx16.r0 = (flag and flag2) as uword
|
||||
}
|
||||
}"""
|
||||
|
||||
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
|
||||
compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
})
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
Loaded 100 of 238 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user