Compare commits

...
24 Commits
Author SHA1 Message Date
Irmen de Jong c86c0912f8 latest symboldumps 2024-12-29 20:07:45 +01:00
Irmen de Jong 268b0c9365 fix memory top boundary address on c64 and c128 2024-12-29 17:01:22 +01:00
Irmen de Jong 099fe280ba improved diskio.diskname() error detection 2024-12-29 10:29:31 +01:00
Irmen de Jong f786f60e9c cmb.PLOT: fixed order of return registers (Y then X, column then row) - same as argument order 2024-12-29 07:42:13 +01:00
Irmen de Jong f40e1eb1f2 fixed clipping sample 2024-12-29 00:56:12 +01:00
Irmen de Jong 8b9da65357 Added supervisor to coroutines.run(). update symboldumps for 11.0.1. 2024-12-28 04:44:45 +01:00
Irmen de Jong 2cbbe0d48a remove syscall from docs, it doesn't exist anymore 2024-12-27 22:35:56 +01:00
Irmen de Jong b6e1fb3ba8 emphasize that str[] is also split by default 2024-12-27 17:00:39 +01:00
Irmen de Jong bdccffbb8e stricter type checking in multivalue assigns, avoids possible invalid output due to missing type cast 2024-12-26 22:20:08 +01:00
Irmen de Jong 5a85474712 pet32: fixed txt.plot() mixing up row and column 2024-12-26 19:37:39 +01:00
Irmen de Jong f50899c6fa coroutines: make yield() return a configured uword so that a task subroutine can get reused for multiple different things 2024-12-26 18:55:32 +01:00
Irmen de Jong 4daa909f32 fix path normalization problems,
allow ~ in srcdirs compiler flag
2024-12-26 17:42:20 +01:00
Irmen de Jong 4555edf369 update zsmkit to fix zsm_clearisr routine 2024-12-26 13:06:41 +01:00
Irmen de Jong 529ea5bf58 added coroutines library and multitasking example. Added sys.push_returnaddress(). 2024-12-26 00:57:39 +01:00
Irmen de Jong fe011de934 fix the missing cases in certain expressions that need the address of a split word array 2024-12-25 16:55:07 +01:00
Irmen de Jong 0653d430a7 fix compiler crash related to uword array parameters type checking 2024-12-25 12:12:20 +01:00
Irmen de Jong a587f6e9a0 make imported module cache case-insensitive
this avoids crashes when using case-insensitive filesystems and mistyping the exact path case
2024-12-25 11:51:24 +01:00
Irmen de Jong 3850e1dbb5 Merge branch 'better-ir' 2024-12-24 21:23:57 +01:00
Irmen de Jong 91cde072e0 added txt.t256c() on the cx16 to turn 256 color tile mode on or off 2024-12-24 12:10:02 +01:00
Irmen de Jong 2ca4aed566 IR: prefix immediate values with '#' for human readability reasons (no technical reason) 2024-12-24 09:35:10 +01:00
Irmen de Jong 5071da6784 retain constants in IR
some IR related cleanups
2024-12-24 00:30:08 +01:00
Irmen de Jong 4c1e2f3110 refactor package nesting of ast exception classes 2024-12-23 18:14:46 +01:00
Irmen de Jong 2727a4dcb3 tweak DataType class and memsizer related to subtypes/elementtypes 2024-12-23 17:28:25 +01:00
Irmen de Jong 126d4c69e6 fix cx16images.py script for new pillow library version 2024-12-23 16:31:15 +01:00
112 changed files with 1202 additions and 667 deletions

No files matched your search

+1
View File
@@ -59,6 +59,7 @@ What does Prog8 provide?
- modularity, symbol scoping, subroutines. No need for forward declarations.
- various data types other than just bytes (16-bit words, floats, strings)
- floating point math is supported on certain targets
- access to most Kernal ROM routines as external subroutine definitions you can call normally
- strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents.
- automatic static variable allocations, automatic string and array variables and string sharing
- high-level program optimizations
+8 -5
View File
@@ -42,6 +42,7 @@ class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GL
}
val allVariables: Collection<StStaticVariable> by lazy {
// can't be done with a generic function because those don't support local recursive functions yet
val vars = mutableListOf<StStaticVariable>()
fun collect(node: StNode) {
for(child in node.children) {
@@ -56,6 +57,7 @@ class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GL
}
val allMemMappedVariables: Collection<StMemVar> by lazy {
// can't be done with a generic function because those don't support local recursive functions yet
val vars = mutableListOf<StMemVar>()
fun collect(node: StNode) {
for(child in node.children) {
@@ -70,6 +72,7 @@ class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GL
}
val allMemorySlabs: Collection<StMemorySlab> by lazy {
// can't be done with a generic function because those don't support local recursive functions yet
val vars = mutableListOf<StMemorySlab>()
fun collect(node: StNode) {
for(child in node.children) {
@@ -113,7 +116,7 @@ enum class StNodeType {
open class StNode(val name: String,
val type: StNodeType,
val astNode: PtNode,
val astNode: PtNode?,
val children: MutableMap<String, StNode> = mutableMapOf()
) {
@@ -187,7 +190,7 @@ class StStaticVariable(name: String,
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
val zpwish: ZeropageWish, // used in the variable allocator
val align: Int,
astNode: PtNode) : StNode(name, StNodeType.STATICVAR, astNode) {
astNode: PtNode?) : StNode(name, StNodeType.STATICVAR, astNode) {
var initializationNumericValue: Double? = null
private set
@@ -229,7 +232,7 @@ class StStaticVariable(name: String,
}
class StConstant(name: String, val dt: BaseDataType, val value: Double, astNode: PtNode) :
class StConstant(name: String, val dt: BaseDataType, val value: Double, astNode: PtNode?) :
StNode(name, StNodeType.CONSTANT, astNode)
@@ -237,7 +240,7 @@ class StMemVar(name: String,
val dt: DataType,
val address: UInt,
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
astNode: PtNode) :
astNode: PtNode?) :
StNode(name, StNodeType.MEMVAR, astNode) {
init{
@@ -251,7 +254,7 @@ class StMemorySlab(
name: String,
val size: UInt,
val align: UInt,
astNode: PtNode
astNode: PtNode?
):
StNode(name, StNodeType.MEMORYSLAB, astNode)
+43 -64
View File
@@ -55,43 +55,18 @@ val BaseDataType.isPassByRef get() = this.isIterable
val BaseDataType.isPassByValue get() = !this.isIterable
sealed class SubType(val dt: BaseDataType) {
companion object {
private val types by lazy {
// lazy because of static initialization order
mapOf(
BaseDataType.UBYTE to SubUnsignedByte,
BaseDataType.BYTE to SubSignedByte,
BaseDataType.UWORD to SubUnsignedWord,
BaseDataType.WORD to SubSignedWord,
BaseDataType.FLOAT to SubFloat,
BaseDataType.BOOL to SubBool
)}
fun forDt(dt: BaseDataType) = types.getValue(dt)
}
}
data object SubUnsignedByte: SubType(BaseDataType.UBYTE)
data object SubSignedByte: SubType(BaseDataType.BYTE)
data object SubUnsignedWord: SubType(BaseDataType.UWORD)
data object SubSignedWord: SubType(BaseDataType.WORD)
data object SubBool: SubType(BaseDataType.BOOL)
data object SubFloat: SubType(BaseDataType.FLOAT)
class DataType private constructor(val base: BaseDataType, val sub: SubType?) {
class DataType private constructor(val base: BaseDataType, val sub: BaseDataType?) {
init {
if(base.isArray) {
require(sub != null)
if(base.isSplitWordArray)
require(sub.dt == BaseDataType.UWORD || sub.dt == BaseDataType.WORD)
require(sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
}
else if(base==BaseDataType.STR)
require(sub?.dt==BaseDataType.UBYTE) { "STR subtype should be ubyte" }
require(sub==BaseDataType.UBYTE) { "string subtype should be ubyte" }
else
require(sub == null)
require(sub == null) { "only string and array base types can have a subtype"}
}
override fun equals(other: Any?): Boolean {
@@ -111,7 +86,7 @@ class DataType private constructor(val base: BaseDataType, val sub: SubType?) {
BaseDataType.LONG to DataType(BaseDataType.LONG, null),
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null),
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null),
BaseDataType.STR to DataType(BaseDataType.STR, SubUnsignedByte),
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE),
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null)
)
@@ -119,10 +94,14 @@ class DataType private constructor(val base: BaseDataType, val sub: SubType?) {
fun arrayFor(elementDt: BaseDataType, splitwordarray: Boolean=true): DataType {
val actualElementDt = if(elementDt==BaseDataType.STR) BaseDataType.UWORD else elementDt // array of strings is actually just an array of UWORD pointers
return if(splitwordarray && elementDt.isWord)
DataType(BaseDataType.ARRAY_SPLITW, SubType.forDt(actualElementDt))
else
DataType(BaseDataType.ARRAY, SubType.forDt(actualElementDt))
return if(splitwordarray && actualElementDt.isWord)
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt)
else {
if(actualElementDt.isNumericOrBool && actualElementDt != BaseDataType.LONG)
DataType(BaseDataType.ARRAY, actualElementDt)
else
throw NoSuchElementException("invalid element dt "+elementDt)
}
}
}
@@ -133,26 +112,26 @@ class DataType private constructor(val base: BaseDataType, val sub: SubType?) {
fun elementType(): DataType =
if(base.isArray || base==BaseDataType.STR)
forDt(sub!!.dt)
forDt(sub!!)
else
throw IllegalArgumentException("not an array")
override fun toString(): String = when(base) {
BaseDataType.ARRAY -> {
when(sub) {
SubBool -> "bool[]"
SubFloat -> "float[]"
SubSignedByte -> "byte[]"
SubSignedWord -> "word[]"
SubUnsignedByte -> "ubyte[]"
SubUnsignedWord -> "uword[]"
null -> throw IllegalArgumentException("invalid sub type")
BaseDataType.BOOL -> "bool[]"
BaseDataType.FLOAT -> "float[]"
BaseDataType.BYTE -> "byte[]"
BaseDataType.WORD -> "word[]"
BaseDataType.UBYTE -> "ubyte[]"
BaseDataType.UWORD -> "uword[]"
else -> throw IllegalArgumentException("invalid sub type")
}
}
BaseDataType.ARRAY_SPLITW -> {
when(sub) {
SubSignedWord -> "word[] (split)"
SubUnsignedWord -> "uword[] (split)"
BaseDataType.WORD -> "word[] (split)"
BaseDataType.UWORD -> "uword[] (split)"
else -> throw IllegalArgumentException("invalid sub type")
}
}
@@ -170,19 +149,19 @@ class DataType private constructor(val base: BaseDataType, val sub: SubType?) {
BaseDataType.STR -> "str"
BaseDataType.ARRAY -> {
when(sub) {
SubUnsignedByte -> "ubyte["
SubUnsignedWord -> "@nosplit uword["
SubBool -> "bool["
SubSignedByte -> "byte["
SubSignedWord -> "@nosplit word["
SubFloat -> "float["
null -> throw IllegalArgumentException("invalid sub type")
BaseDataType.UBYTE -> "ubyte["
BaseDataType.UWORD -> "@nosplit uword["
BaseDataType.BOOL -> "bool["
BaseDataType.BYTE -> "byte["
BaseDataType.WORD -> "@nosplit word["
BaseDataType.FLOAT -> "float["
else -> throw IllegalArgumentException("invalid sub type")
}
}
BaseDataType.ARRAY_SPLITW -> {
when(sub) {
SubUnsignedWord -> "uword["
SubSignedWord -> "word["
BaseDataType.UWORD -> "uword["
BaseDataType.WORD -> "word["
else -> throw IllegalArgumentException("invalid sub type")
}
}
@@ -228,22 +207,22 @@ class DataType private constructor(val base: BaseDataType, val sub: SubType?) {
val isSigned = base.isSigned
val isUnsigned = !base.isSigned
val isArray = base.isArray
val isBoolArray = base.isArray && sub?.dt == BaseDataType.BOOL
val isByteArray = base.isArray && (sub?.dt == BaseDataType.UBYTE || sub?.dt == BaseDataType.BYTE)
val isUnsignedByteArray = base.isArray && sub?.dt == BaseDataType.UBYTE
val isSignedByteArray = base.isArray && sub?.dt == BaseDataType.BYTE
val isWordArray = base.isArray && (sub?.dt == BaseDataType.UWORD || sub?.dt == BaseDataType.WORD)
val isUnsignedWordArray = base.isArray && sub?.dt == BaseDataType.UWORD
val isSignedWordArray = base.isArray && sub?.dt == BaseDataType.WORD
val isFloatArray = base.isArray && sub?.dt == BaseDataType.FLOAT
val isBoolArray = base.isArray && sub == BaseDataType.BOOL
val isByteArray = base.isArray && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE)
val isUnsignedByteArray = base.isArray && sub == BaseDataType.UBYTE
val isSignedByteArray = base.isArray && sub == BaseDataType.BYTE
val isWordArray = base.isArray && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
val isUnsignedWordArray = base.isArray && sub == BaseDataType.UWORD
val isSignedWordArray = base.isArray && sub == BaseDataType.WORD
val isFloatArray = base.isArray && sub == BaseDataType.FLOAT
val isString = base == BaseDataType.STR
val isBool = base == BaseDataType.BOOL
val isFloat = base == BaseDataType.FLOAT
val isLong = base == BaseDataType.LONG
val isStringly = base == BaseDataType.STR || base == BaseDataType.UWORD || (base == BaseDataType.ARRAY && (sub?.dt == BaseDataType.UBYTE || sub?.dt == BaseDataType.BYTE))
val isStringly = base == BaseDataType.STR || base == BaseDataType.UWORD || (base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE))
val isSplitWordArray = base.isSplitWordArray
val isSplitUnsignedWordArray = base.isSplitWordArray && sub?.dt == BaseDataType.UWORD
val isSplitSignedWordArray = base.isSplitWordArray && sub?.dt == BaseDataType.WORD
val isSplitUnsignedWordArray = base.isSplitWordArray && sub == BaseDataType.UWORD
val isSplitSignedWordArray = base.isSplitWordArray && sub == BaseDataType.WORD
val isIterable = base.isIterable
val isPassByRef = base.isPassByRef
val isPassByValue = base.isPassByValue
+10 -1
View File
@@ -2,5 +2,14 @@ package prog8.code.core
interface IMemSizer {
fun memorySize(dt: DataType, numElements: Int?): Int
fun memorySize(dt: SubType): Int
fun memorySize(dt: BaseDataType): Int {
if(dt.isPassByRef)
return memorySize(DataType.forDt(BaseDataType.UWORD), null) // a pointer size
try {
return memorySize(DataType.forDt(dt), null)
} catch (x: NoSuchElementException) {
throw IllegalArgumentException(x.message)
}
}
}
@@ -90,7 +90,7 @@ private fun optimizeBitTest(program: PtProgram, options: CompilationOptions): In
return bittestCall
}
fun isAndByteCondition(condition: PtBinaryExpression?): Triple<PtBinaryExpression, PtIdentifier, Int>? {
fun isAndByteConditionForBRK(condition: PtBinaryExpression?): Triple<PtBinaryExpression, PtIdentifier, Int>? {
if(condition!=null && (condition.operator=="==" || condition.operator=="!=")) {
if (condition.right.asConstInteger() == 0) {
val and = condition.left as? PtBinaryExpression
@@ -119,7 +119,7 @@ private fun optimizeBitTest(program: PtProgram, options: CompilationOptions): In
walkAst(program) { node: PtNode, depth: Int ->
if(node is PtIfElse) {
val condition = node.condition as? PtBinaryExpression
val check = isAndByteCondition(condition)
val check = isAndByteConditionForBRK(condition)
if(check!=null) {
val (and, variable, bitmask) = check
val bittestCall = makeBittestCall(condition!!, and, variable, bitmask)
@@ -137,7 +137,7 @@ private fun optimizeBitTest(program: PtProgram, options: CompilationOptions): In
}
if (node is PtIfExpression) {
val condition = node.condition as? PtBinaryExpression
val check = isAndByteCondition(condition)
val check = isAndByteConditionForBRK(condition)
if(check!=null) {
val (and, variable, bitmask) = check
val bittestCall = makeBittestCall(condition!!, and, variable, bitmask)
@@ -2,18 +2,32 @@ package prog8.code.source
import prog8.code.core.Position
import java.nio.file.Path
import java.util.TreeMap
import kotlin.io.path.Path
import kotlin.io.path.absolute
// Resource caching "filesystem".
// Note that it leaves the decision to load a resource or an actual disk file to the caller.
object ImportFileSystem {
fun expandTilde(path: String): String = if (path.startsWith("~")) {
val userHome = System.getProperty("user.home")
userHome + path.drop(1)
} else {
path
}
fun expandTilde(path: Path): Path = Path(expandTilde(path.toString()))
fun getFile(path: Path): SourceCode {
val cached = cache[path.toString()]
if (cached != null) return cached
val file = SourceCode.File(path)
cache[path.toString()] = file
val normalized = path.absolute().normalize()
val cached = cache[normalized.toString()]
if (cached != null)
return cached
val file = SourceCode.File(normalized)
cache[normalized.toString()] = file
return file
}
@@ -34,11 +48,11 @@ object ImportFileSystem {
val cached = cache[position.file]
if(cached != null)
return getLine(cached, position.line)
val path = Path(position.file).toAbsolutePath().normalize()
val path = Path(position.file).absolute().normalize()
val cached2 = cache[path.toString()]
if(cached2 != null)
return getLine(cached2, position.line)
throw NoSuchElementException("cannot get source line $position")
throw NoSuchElementException("cannot get source line $position, with path $path")
}
private fun getLine(code: SourceCode, lineIndex: Int): String {
@@ -55,6 +69,6 @@ object ImportFileSystem {
private class LineSpan(val start: Int, val end: Int)
private val cache = mutableMapOf<String, SourceCode>()
private val cache = TreeMap<String, SourceCode>(String.CASE_INSENSITIVE_ORDER)
private val lineSpanCache = mutableMapOf<SourceCode, Array<LineSpan>>()
}
+3 -2
View File
@@ -4,6 +4,7 @@ import java.io.IOException
import java.nio.file.Path
import java.text.Normalizer
import kotlin.io.path.Path
import kotlin.io.path.absolute
import kotlin.io.path.readText
/**
@@ -52,8 +53,8 @@ sealed class SourceCode {
*/
private const val LIBRARYFILEPREFIX = "library:"
private const val STRINGSOURCEPREFIX = "string:"
val curdir: Path = Path(".").toAbsolutePath()
fun relative(path: Path): Path = curdir.relativize(path.toAbsolutePath())
val curdir: Path = Path(".").absolute()
fun relative(path: Path): Path = curdir.relativize(path.absolute())
fun isRegularFilesystemPath(pathString: String) = !isLibraryResource(pathString) && !isStringResource(pathString)
fun isLibraryResource(path: String) = path.startsWith(LIBRARYFILEPREFIX)
fun isStringResource(path: String) = path.startsWith(STRINGSOURCEPREFIX)
+10 -7
View File
@@ -15,23 +15,26 @@ class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
require(numElements!=null)
return when(dt.sub?.dt) {
if(numElements==null) return 2 // treat it as a pointer size
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements * 2
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2
BaseDataType.FLOAT-> numElements * machine.FLOAT_MEM_SIZE
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
}
}
else if (dt.isString) {
if(numElements!=null) return numElements // treat it as the size of the given string with the length
else return 2 // treat it as the size to store a string pointer
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> machine.FLOAT_MEM_SIZE * (numElements ?: 1)
dt.isLong -> throw IllegalArgumentException("long can not yet be put into memory")
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
else -> 2 * (numElements ?: 1)
}
}
override fun memorySize(dt: SubType): Int {
return memorySize(DataType.forDt(dt.dt), null)
}
}
@@ -15,23 +15,26 @@ class Neo6502Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
require(numElements!=null)
return when(dt.sub?.dt) {
if(numElements==null) return 2 // treat it as a pointer size
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements * 2
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2
BaseDataType.FLOAT-> numElements * machine.FLOAT_MEM_SIZE
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
}
}
else if (dt.isString) {
if(numElements!=null) return numElements // treat it as the size of the given string with the length
else return 2 // treat it as the size to store a string pointer
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> machine.FLOAT_MEM_SIZE * (numElements ?: 1)
dt.isLong -> throw IllegalArgumentException("long can not yet be put into memory")
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
else -> 2 * (numElements ?: 1)
}
}
override fun memorySize(dt: SubType): Int {
return memorySize(DataType.forDt(dt.dt), null)
}
}
+11 -8
View File
@@ -14,23 +14,26 @@ class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
require(numElements!=null)
return when(dt.sub?.dt) {
if(numElements==null) return 2 // treat it as a pointer size
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements * 2
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2
BaseDataType.FLOAT-> numElements * machine.FLOAT_MEM_SIZE
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
}
}
else if (dt.isString) {
if(numElements!=null) return numElements // treat it as the size of the given string with the length
else return 2 // treat it as the size to store a string pointer
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> machine.FLOAT_MEM_SIZE * (numElements ?: 1)
dt.isLong -> throw IllegalArgumentException("long can not yet be put into memory")
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
else -> 2 * (numElements ?: 1)
}
}
override fun memorySize(dt: SubType): Int {
return memorySize(DataType.forDt(dt.dt), null)
}
}
@@ -15,7 +15,7 @@ class C128MachineDefinition: IMachineDefinition {
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
override val PROGRAM_MEMTOP_ADDRESS = 0xff00u
override val PROGRAM_MEMTOP_ADDRESS = 0xc000u
override val BSSHIGHRAM_START = 0u // TODO
override val BSSHIGHRAM_END = 0u // TODO
@@ -20,7 +20,7 @@ class C64MachineDefinition: IMachineDefinition {
// note that at $cfe0-$cfff are the 16 'virtual registers' R0-R15
override val BSSHIGHRAM_START = 0xc000u
override val BSSHIGHRAM_END = 0xcfffu
override val BSSHIGHRAM_END = 0xcfdfu
override val BSSGOLDENRAM_START = 0u
override val BSSGOLDENRAM_END = 0u
@@ -3,28 +3,31 @@ package prog8.code.target.cbm
import prog8.code.core.BaseDataType
import prog8.code.core.DataType
import prog8.code.core.IMemSizer
import prog8.code.core.SubType
internal object CbmMemorySizer: IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
require(numElements!=null)
return when(dt.sub?.dt) {
if(numElements==null) return 2 // treat it as a pointer size
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements * 2
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2
BaseDataType.FLOAT-> numElements * Mflpt5.FLOAT_MEM_SIZE
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
}
}
else if (dt.isString) {
if(numElements!=null) return numElements // treat it as the size of the given string with the length
else return 2 // treat it as the size to store a string pointer
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> Mflpt5.FLOAT_MEM_SIZE * (numElements ?: 1)
dt.isLong -> throw IllegalArgumentException("long can not yet be put into memory")
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
else -> 2 * (numElements ?: 1)
}
}
override fun memorySize(dt: SubType): Int {
return memorySize(DataType.forDt(dt.dt), null)
}
}
@@ -12,6 +12,7 @@ import prog8.code.source.SourceCode
import prog8.code.target.Cx16Target
import prog8.codegen.cpu6502.assignment.*
import kotlin.io.path.Path
import kotlin.io.path.absolute
import kotlin.io.path.writeLines
@@ -59,7 +60,7 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
}
is PtFunctionCall -> {
val stNode = st.lookup(node.name)!!
if(stNode.astNode.definingBlock()?.options?.noSymbolPrefixing!=true) {
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
functionCallsToPrefix += node.parent to index
}
@@ -70,7 +71,7 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
lookupName = lookupName.dropLast(4)
}
val stNode = st.lookup(lookupName) ?: throw AssemblyError("unknown identifier $node")
if(stNode.astNode.definingBlock()?.options?.noSymbolPrefixing!=true) {
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
nodesToPrefix += node.parent to index
}
@@ -1083,8 +1084,8 @@ $repeatLabel""")
val sourcePath = Path(incbin.definingBlock()!!.source.origin)
val includedPath = sourcePath.resolveSibling(incbin.file)
val pathForAssembler = options.outputDir // #54: 64tass needs the path *relative to the .asm file*
.toAbsolutePath()
.relativize(includedPath.toAbsolutePath())
.absolute()
.relativize(includedPath.absolute())
.normalize() // avoid assembler warnings (-Wportable; only some, not all)
.toString().replace('\\', '/')
out(" .binary \"$pathForAssembler\" $offset $length")
@@ -1268,7 +1269,7 @@ $repeatLabel""")
val node = stScope.astNode
if(node is PtSubroutineParameter)
return node
return node.definingSub()?.parameters?.singleOrNull { it.name===name }
return node!!.definingSub()?.parameters?.singleOrNull { it.name===name }
}
internal fun assignByteOperandsToAAndVar(left: PtExpression, right: PtExpression, rightVarName: String) {
@@ -644,32 +644,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val varname = asmgen.asmVariableName(fcall.args[0] as PtIdentifier) + if(msb) "+1" else ""
target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.forDt(BaseDataType.UBYTE), fcall.definingSub(), fcall.position, variableAsmName = varname)
}
is PtNumber -> {
val num = (fcall.args[0] as PtNumber).number + if(msb) 1 else 0
val mem = PtMemoryByte(fcall.position)
mem.add(PtNumber(BaseDataType.UBYTE, num, fcall.position))
target = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.forDt(BaseDataType.UBYTE), fcall.definingSub(), fcall.position, memory = mem)
}
is PtAddressOf -> {
val addrof = fcall.args[0] as PtAddressOf
if(addrof.identifier.type.isSplitWordArray) {
TODO("address of split word array")
} else {
val mem = PtMemoryByte(fcall.position)
if(addrof.isFromArrayElement)
TODO("address-of arrayelement")
if(msb) {
val address = PtBinaryExpression("+", DataType.forDt(BaseDataType.UWORD), addrof.position)
address.add(addrof)
address.add(PtNumber(address.type.base, 1.0, addrof.position))
mem.add(address)
} else {
mem.add(addrof)
}
target = AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, DataType.forDt(BaseDataType.UBYTE), fcall.definingSub(), fcall.position, memory = mem)
}
}
is PtArrayIndexer -> {
val indexer = fcall.args[0] as PtArrayIndexer
val elementSize: Int
@@ -808,11 +808,11 @@ _jump jmp (${target.asmLabel})
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.out(" cpy #0")
} else {
var varname = asmgen.asmVariableName(value.identifier)
if(value.identifier.type.isSplitWordArray) {
TODO("address of split word array")
} else {
asmgen.out(" lda #>${asmgen.asmVariableName(value.identifier)}")
varname += if(value.isMsbForSplitArray) "_msb" else "_lsb"
}
asmgen.out(" lda #>$varname")
}
}
else -> {
@@ -1607,13 +1607,13 @@ _jump jmp (${target.asmLabel})
if(left.isFromArrayElement)
fallbackTranslateForSimpleCondition(stmt)
else {
if(left.identifier.type.isSplitWordArray) {
TODO("address of split word array")
val varname = if(left.identifier.type.isSplitWordArray) {
if(left.isMsbForSplitArray) left.identifier.name+"_msb" else left.identifier.name+"_lsb"
} else {
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
val varname = left.identifier.name
translateAYNotEquals("#<$varname", "#>$varname")
left.identifier.name
}
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
translateAYNotEquals("#<$varname", "#>$varname")
}
}
else -> {
@@ -1659,13 +1659,13 @@ _jump jmp (${target.asmLabel})
if(left.isFromArrayElement)
fallbackTranslateForSimpleCondition(stmt)
else {
if(left.identifier.type.isSplitWordArray) {
TODO("address of split word array")
val varname = if(left.identifier.type.isSplitWordArray) {
if(left.isMsbForSplitArray) left.identifier.name+"_msb" else left.identifier.name+"_lsb"
} else {
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
val varname = left.identifier.name
translateAYEquals("#<$varname", "#>$varname")
left.identifier.name
}
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
translateAYEquals("#<$varname", "#>$varname")
}
}
else -> {
@@ -60,7 +60,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
variable.scopedName,
variable.dt,
variable.length,
variable.astNode.position,
variable.astNode?.position ?: Position.DUMMY,
errors
)
result.fold(
@@ -68,7 +68,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
numVariablesAllocatedInZP++
},
failure = {
errors.err(it.message!!, variable.astNode.position)
errors.err(it.message!!, variable.astNode?.position ?: Position.DUMMY)
}
)
}
@@ -79,7 +79,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
variable.scopedName,
variable.dt,
variable.length,
variable.astNode.position,
variable.astNode?.position ?: Position.DUMMY,
errors
)
result.onSuccess { numVariablesAllocatedInZP++ }
@@ -99,7 +99,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
variable.scopedName,
variable.dt,
variable.length,
variable.astNode.position,
variable.astNode?.position ?: Position.DUMMY,
errors
)
result.onSuccess { numVariablesAllocatedInZP++ }
@@ -1341,13 +1341,12 @@ internal class AssignmentAsmGen(
when (right) {
is PtAddressOf -> {
val symbol = asmgen.asmVariableName(right.identifier)
var symbol = asmgen.asmVariableName(right.identifier)
if(right.isFromArrayElement) {
TODO("address-of array element $symbol at ${right.position}")
} else {
if(right.identifier.type.isSplitWordArray) {
TODO("address of split word array")
return true
symbol = if(right.isMsbForSplitArray) symbol+"_msb" else symbol+"_lsb"
}
assignExpressionToRegister(left, RegisterOrPair.AY, dt.isSigned)
if(expr.operator=="+")
@@ -2579,7 +2578,8 @@ $endLabel""")
private fun assignAddressOf(target: AsmAssignTarget, sourceName: String, arrayDt: DataType?, arrayIndexExpr: PtExpression?) {
if(arrayIndexExpr!=null) {
require(arrayDt?.isSplitWordArray!=true)
if(arrayDt?.isSplitWordArray==true)
TODO("address of element of a split word array")
val constIndex = arrayIndexExpr.asConstInteger()
if(constIndex!=null) {
if (arrayDt?.isUnsignedWord==true) {
@@ -2930,14 +2930,12 @@ $endLabel""")
storeRegisterAInMemoryAddress(target.memory!!)
}
TargetStorageKind.ARRAY -> {
if(target.array!!.splitWords)
TODO("assign into split words ${target.position}")
if (target.constArrayIndexValue!=null) {
val scaledIdx = program.memsizer.memorySize(target.datatype, target.constArrayIndexValue!!.toInt())
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
}
else {
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, CpuRegister.Y)
asmgen.out(" lda $sourceName | sta ${target.asmVarname},y")
}
}
@@ -2979,19 +2977,33 @@ $endLabel""")
""")
}
TargetStorageKind.ARRAY -> {
if(wordtarget.array!!.splitWords)
TODO("assign byte into split words ${wordtarget.position}")
if (wordtarget.constArrayIndexValue!=null) {
val scaledIdx = wordtarget.constArrayIndexValue!! * 2u
asmgen.out(" lda $sourceName")
asmgen.signExtendAYlsb(BaseDataType.BYTE)
asmgen.out(" sta ${wordtarget.asmVarname}+$scaledIdx | sty ${wordtarget.asmVarname}+$scaledIdx+1")
if(wordtarget.array!!.splitWords) {
// signed byte, we must sign-extend
if (wordtarget.constArrayIndexValue!=null) {
val scaledIdx = wordtarget.constArrayIndexValue!!
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}_lsb+$scaledIdx")
asmgen.signExtendAYlsb(BaseDataType.BYTE)
asmgen.out(" sty ${wordtarget.asmVarname}_msb+$scaledIdx")
}
else {
asmgen.loadScaledArrayIndexIntoRegister(wordtarget.array, CpuRegister.X)
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}_msb,x")
asmgen.signExtendAYlsb(BaseDataType.BYTE)
asmgen.out(" tya | sta ${wordtarget.asmVarname}_msb,x")
}
}
else {
asmgen.loadScaledArrayIndexIntoRegister(wordtarget.array, CpuRegister.X)
asmgen.out(" lda $sourceName")
asmgen.signExtendAYlsb(BaseDataType.BYTE)
asmgen.out(" sta ${wordtarget.asmVarname},x | inx | tya | sta ${wordtarget.asmVarname},x")
if (wordtarget.constArrayIndexValue != null) {
val scaledIdx = wordtarget.constArrayIndexValue!! * 2u
asmgen.out(" lda $sourceName")
asmgen.signExtendAYlsb(BaseDataType.BYTE)
asmgen.out(" sta ${wordtarget.asmVarname}+$scaledIdx | sty ${wordtarget.asmVarname}+$scaledIdx+1")
} else {
asmgen.loadScaledArrayIndexIntoRegister(wordtarget.array, CpuRegister.X)
asmgen.out(" lda $sourceName")
asmgen.signExtendAYlsb(BaseDataType.BYTE)
asmgen.out(" sta ${wordtarget.asmVarname},x | inx | tya | sta ${wordtarget.asmVarname},x")
}
}
}
TargetStorageKind.REGISTER -> {
@@ -3700,8 +3712,7 @@ $endLabel""")
storeRegisterAInMemoryAddress(target.memory!!)
}
TargetStorageKind.ARRAY -> {
if(target.array!!.splitWords)
TODO("assign into split words ${target.position}")
require(!target.array!!.splitWords)
if (target.constArrayIndexValue!=null) {
val indexValue = target.constArrayIndexValue!!
asmgen.out(" lda #${byte.toHex()} | sta ${target.asmVarname}+$indexValue")
+3 -3
View File
@@ -7,7 +7,7 @@ internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
require(numElements != null)
return when(dt.sub?.dt) {
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
BaseDataType.FLOAT -> numElements*5
@@ -21,8 +21,8 @@ internal object DummyMemsizer : IMemSizer {
}
}
override fun memorySize(dt: SubType): Int {
return memorySize(DataType.forDt(dt.dt), null)
override fun memorySize(dt: BaseDataType): Int {
return memorySize(DataType.forDt(dt), null)
}
}
@@ -904,10 +904,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
}
return result
} else {
return null // TODO("inplace split word array +")
return null // TODO("inplace split word array -")
}
}
return null // TODO("inplace split word array +")
return null // TODO("inplace split word array -")
}
private fun operatorPlusInplace(symbol: String?, array: PtArrayIndexer?, constAddress: Int?, memory: PtMemoryByte?, vmDt: IRDataType, operand: PtExpression): IRCodeChunks? {
@@ -133,7 +133,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.nextFree()
if(expr.isFromArrayElement) {
require(!expr.identifier.type.isSplitWordArray)
if(expr.identifier.type.isSplitWordArray)
TODO("address of element of a split word array")
addInstr(result, IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = symbol), null)
val indexTr2 = translateExpression(expr.arrayIndexExpr!!)
addToResult(result, indexTr2, indexTr2.resultReg, -1)
@@ -25,7 +25,7 @@ class IRCodeGen(
verifyNameScoping(program, symbolTable)
changeGlobalVarInits(symbolTable)
val irSymbolTable = IRSymbolTable.fromStDuringCodegen(symbolTable)
val irSymbolTable = IRSymbolTable.fromAstSymboltable(symbolTable)
val irProg = IRProgram(program.name, irSymbolTable, options, program.encoding)
// collect global variables initializers
@@ -477,9 +477,8 @@ class IRCodeGen(
}
else -> {
// iterate over regular array
val element = iterable.type.sub!!
val elementDt = element.dt
val elementSize = program.memsizer.memorySize(element)
val elementDt = iterable.type.sub!!
val elementSize = program.memsizer.memorySize(elementDt)
val lengthBytes = iterableLength!! * elementSize
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
@@ -487,7 +486,7 @@ class IRCodeGen(
it += IRInstruction(Opcode.STOREM, irType(DataType.forDt(elementDt)), reg1=tmpReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
result += addConstReg(IRDataType.BYTE, indexReg, elementSize)
result += addConstIntToReg(IRDataType.BYTE, indexReg, elementSize)
result += IRCodeChunk(null, null).also {
if(lengthBytes!=256) {
// for length 256, the compare is actually against 0, which doesn't require a separate CMP instruction
@@ -640,7 +639,7 @@ class IRCodeGen(
return result
}
private fun addConstReg(dt: IRDataType, reg: Int, value: Int): IRCodeChunk {
private fun addConstIntToReg(dt: IRDataType, reg: Int, value: Int): IRCodeChunk {
val code = IRCodeChunk(null, null)
when(value) {
0 -> { /* do nothing */ }
@@ -1349,7 +1348,7 @@ class IRCodeGen(
val condition = ifElse.condition as? PtBinaryExpression
if(condition==null || !condition.left.type.isFloat) {
return ifWithElse_IntegerCond(ifElse)
return ifElse_IntegerCond(ifElse)
}
// we assume only a binary expression can contain a floating point.
@@ -1409,7 +1408,7 @@ class IRCodeGen(
return result
}
private fun ifWithElse_IntegerCond(ifElse: PtIfElse): List<IRCodeChunkBase> {
private fun ifElse_IntegerCond(ifElse: PtIfElse): List<IRCodeChunkBase> {
val result = mutableListOf<IRCodeChunkBase>()
fun translateSimple(condition: PtExpression, jumpFalseOpcode: Opcode, addCmpiZero: Boolean) {
+3 -3
View File
@@ -5,7 +5,7 @@ internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray || dt.isSplitWordArray) {
require(numElements!=null)
return when(dt.sub?.dt) {
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
BaseDataType.FLOAT -> numElements*5
@@ -19,8 +19,8 @@ internal object DummyMemsizer : IMemSizer {
}
}
override fun memorySize(dt: SubType): Int {
return memorySize(DataType.forDt(dt.dt), null)
override fun memorySize(dt: BaseDataType): Int {
return memorySize(DataType.forDt(dt), null)
}
}
@@ -1,8 +1,8 @@
package prog8.optimizer
import prog8.ast.ExpressionError
import prog8.ast.FatalAstException
import prog8.ast.Program
import prog8.ast.base.ExpressionError
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.NumericLiteral
import prog8.code.core.BaseDataType
@@ -1,8 +1,8 @@
package prog8.optimizer
import prog8.ast.FatalAstException
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.maySwapOperandOrder
import prog8.ast.statements.*
@@ -1,11 +1,11 @@
package prog8.optimizer
import prog8.ast.FatalAstException
import prog8.ast.IFunctionCall
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.base.UndefinedSymbolError
import prog8.ast.UndefinedSymbolError
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
@@ -438,7 +438,7 @@ internal class ConstantIdentifierReplacer(
} else {
require(rangeType.sub!=null)
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(rangeType.sub!!.dt, it.toDouble(), decl.value!!.position) }.toTypedArray(),
constRange.map { NumericLiteral(rangeType.sub!!, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
}
@@ -1,7 +1,6 @@
package prog8.optimizer
import prog8.ast.*
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.IfElse
+14
View File
@@ -367,6 +367,20 @@ save_SCRATCH_ZPWORD2 .word 0
}}
}
inline asmsub push_returnaddress(uword address @XY) {
%asm {{
; push like JSR would: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
pha
txa
pha
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
+15 -1
View File
@@ -97,7 +97,7 @@ extsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C
extsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
extsub $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!
extsub $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!
extsub $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.
extsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ Y, ubyte @ X ; read/set position of cursor on screen (Y=column, X=row). Also see txt.plot
extsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
; ---- end of C64 compatible ROM kernal routines ----
@@ -918,6 +918,20 @@ _no_msb_size
}}
}
inline asmsub push_returnaddress(uword address @XY) {
%asm {{
; push like JSR would: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
pha
txa
pha
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
+15 -1
View File
@@ -99,7 +99,7 @@ extsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A
extsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
extsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
extsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get size of text screen into X (columns) and Y (rows)
extsub $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.
extsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ Y, ubyte @ X ; read/set position of cursor on screen (Y=column, X=row). Also see txt.plot
extsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
@@ -934,6 +934,20 @@ _no_msb_size
}}
}
inline asmsub push_returnaddress(uword address @XY) {
%asm {{
; push like JSR would: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
pha
txa
pha
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
+146
View File
@@ -0,0 +1,146 @@
; Cooperative multitasking / Coroutines
; EXPERIMENTAL LIBRARY: Api may change or it may be removed completely in a future version!
; Achieves cooperative multitasking among a list of tasks each calling yield() to pass control to the next.
; Uses cpu stack return address juggling to cycle between different tasks.
;
; Features:
; - can have a dynamic number of active tasks (max 64), when a task ends it is automatically removed from the task list.
; - you can add new tasks, while the rest is already running. Just not yet from inside IRQ handlers!
; - tasks are regular subroutines but have to call yield() to pass control to the next task (round-robin)
; - yield() returns the registered userdata value for that task, so a single subroutine could be used as multiple tasks on different userdata
; BUT!! in that case, the subroutine cannot have any variables of its own that keep state, because they're shared across the multiple tasks!
; (if a subroutine is just inserted as a task exactly ONCE, it's okay to use normal variables for state, because nobody will share them)
; - you can kill a task (if you know it's id...)
; - when all tasks are finished the run() call will also return.
; - tasks can't push anything on the cpu stack before calling yield() - that will cause chaos.
; - this library is not (yet) usable from IRQ handlers. Don't do it. It will end badly. (can't manipulate the task list simultaneously)
;
; Difference from IRQ handlers:
; - you can have many tasks instead of only 2 (main program + irq handler)
; - it's not tied to any IRQ setup, and will run as fast as the tasks themselves allow
; - tasks fully control the switch to the next task; there is no preemptive switching
; - tasks will need to save/restore their own state, maybe by useing the userdata (pointer?) and/or task id for that.
;
; USAGE:
; - call add(taskaddress) to add a new task. It returns the task id.
; - call run(supervisor) to start executing all tasks until none are left. Pass 0 or a pointer to a 'supervisor' routine.
; that routine can for instance call current() (or just look at the active_task variable) to get the id of the next task to execute.
; It has then to return a boolean: true=next task is to be executed, false=skip the task this time.
; - in tasks: call yield() to pass control to the next task. Use the returned userdata value to do different things.
; - call current() to get the current task id.
; - call kill(taskid) to kill a task by id.
; - call killall() to kill all tasks.
; - IMPORTANT: if you add the same subroutine multiple times, IT CANNOT DEPEND ON ANY LOCAL VARIABLES OR R0-R15 TO KEEP STATE. NOT EVEN REPEAT LOOP COUNTERS.
; Those are all shared in the different tasks! You HAVE to use a mechanism around the userdata value (pointer?) to keep separate state elsewhere!
coroutines {
const ubyte MAX_TASKS = 64
uword[MAX_TASKS] tasklist
uword[MAX_TASKS] userdatas
uword[MAX_TASKS] returnaddresses
ubyte active_task
uword supervisor
sub add(uword taskaddress, uword userdata) -> ubyte {
; find the next empty slot in the tasklist and stick it there
; returns the task id of the new task, or 255 if there was no space for more tasks. 0 is a valid task id!
; also returns the success in the Carry flag (carry set=success, carry clear = task was not added)
for cx16.r0L in 0 to len(tasklist)-1 {
if tasklist[cx16.r0L] == 0 {
tasklist[cx16.r0L] = taskaddress
userdatas[cx16.r0L] = userdata
returnaddresses[cx16.r0L] = 0
sys.set_carry()
return cx16.r0L
}
}
; no space for new task
sys.clear_carry()
return 255
}
sub killall() {
; kill all existing tasks
for cx16.r0L in 0 to len(tasklist)-1 {
kill(cx16.r0L)
}
}
sub run(uword supervisor_routine) {
supervisor = supervisor_routine
for active_task in 0 to len(tasklist)-1 {
if tasklist[active_task]!=0 {
; activate the termination handler and start the first task
; note: cannot use pushw() because JSR doesn't push the return address in the same way
sys.push_returnaddress(&termination)
goto tasklist[active_task]
}
}
}
sub yield() -> uword {
; Store the return address of the yielding task,
; and continue with the next one instead (round-robin)
; Returns the associated userdata value
uword task_start, task_continue
returnaddresses[active_task] = sys.popw()
resume_with_next_task:
if not next_task() {
void sys.popw() ; remove return to the termination handler
return 0 ; exiting here will now actually return from the start() call back to the calling program :)
}
if supervisor!=0 {
if lsb(call(supervisor))==0
goto resume_with_next_task
}
if task_continue==0 {
; fetch start address of next task.
; address on the stack must be pushed in reverse byte order
; also, subtract 1 from the start address because JSR pushes returnaddress minus 1
; note: cannot use pushw() because JSR doesn't push the return address in the same way
sys.push_returnaddress(task_start)
} else
sys.pushw(task_continue)
; returning from yield then continues with the next coroutine
return userdatas[active_task]
sub next_task() -> bool {
; search through the task list for the next active task
repeat len(tasklist) {
active_task++
if active_task==len(returnaddresses)
active_task=0
task_start = tasklist[active_task]
if task_start!=0 {
task_continue = returnaddresses[active_task]
return true
}
}
return false ; no task
}
}
sub kill(ubyte taskid) {
tasklist[taskid] = 0
returnaddresses[taskid] = 0
}
sub current() -> ubyte {
return active_task
}
sub termination() {
; a task has terminated. wipe it from the list.
; this is an internal routine
kill(active_task)
; reactivate this termination handler
; note: cannot use pushw() because JSR doesn't push the return address in the same way
sys.push_returnaddress(&termination)
goto coroutines.yield.resume_with_next_task
}
}
+14 -9
View File
@@ -104,15 +104,20 @@ io_error:
}
sub diskname() -> uword {
; returns disk label name or 0 if error
cbm.SETNAM(3, "$")
; -- Returns pointer to disk name string or 0 if failure.
cbm.SETNAM(1, "$")
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
ubyte status = 1
void cbm.OPEN() ; open 12,8,0,"$=c"
bool okay = false
void cbm.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
reset_read_channel()
void cbm.CHRIN()
if cbm.READST()!=0
goto io_error
while cbm.CHRIN()!='"' {
; skip up to entry name
}
@@ -130,14 +135,14 @@ io_error:
}
cx16.r0++
}
status = cbm.READST()
okay = true
io_error:
cbm.CLRCHN()
cbm.CLRCHN() ; restore default i/o devices
cbm.CLOSE(READ_IO_CHANNEL)
if status!=0 and status & $40 == 0
return 0
return list_filename
if okay
return &list_filename
return 0
}
; internal variables for the iterative file lister / loader
+1
View File
@@ -588,6 +588,7 @@ gfx_hires {
; Non-recursive scanline flood fill.
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
; Also see https://lodev.org/cgtutor/floodfill.html
word @zp xx = x as word
word @zp yy = y as word
word x1
+1
View File
@@ -640,6 +640,7 @@ gfx_lores {
; Non-recursive scanline flood fill.
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
; Also see https://lodev.org/cgtutor/floodfill.html
word @zp xx = x as word
word @zp yy = y as word
word x1
+1
View File
@@ -739,6 +739,7 @@ invert:
; Non-recursive scanline flood fill.
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
; Also see https://lodev.org/cgtutor/floodfill.html
word @zp xx = x as word
word @zp yy = y as word
word x1
+13 -1
View File
@@ -81,7 +81,7 @@ extsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A
extsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
extsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
extsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get size of text screen into X (columns) and Y (rows)
extsub $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
extsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ Y, ubyte @ X ; read/set position of cursor on screen (Y=column, X=row). Also see txt.plot
extsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
; ---- utility
@@ -1848,6 +1848,18 @@ save_SCRATCH_ZPWORD2 .word 0
}}
}
inline asmsub push_returnaddress(uword address @XY) {
%asm {{
; push like JSR would: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
phy
phx
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
+8
View File
@@ -666,4 +666,12 @@ asmsub print_lit(str text @ AY) clobbers(A,Y) {
}}
}
sub t256c(bool enable) {
; set 256 color tile mode on or off
if enable
cx16.VERA_L1_CONFIG |= 8
else
cx16.VERA_L1_CONFIG &= ~8
}
}
+14
View File
@@ -300,6 +300,20 @@ save_SCRATCH_ZPWORD2 .word 0
}}
}
inline asmsub push_returnaddress(uword address @XY) {
%asm {{
; push like JSR would: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
pha
txa
pha
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
+14
View File
@@ -465,6 +465,20 @@ save_SCRATCH_ZPWORD2 .word 0
}}
}
inline asmsub push_returnaddress(uword address @XY) {
%asm {{
; push like JSR would: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
pha
txa
pha
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
+2 -2
View File
@@ -225,13 +225,13 @@ asmsub plot (ubyte col @ Y, ubyte row @ X) {
jsr home
cpy #0
beq +
- lda #17
- lda #29
jsr chrout
dey
bne -
+ cpx #0
beq +
- lda #29
- lda #17
jsr chrout
dex
bne -
+4 -2
View File
@@ -92,11 +92,13 @@ io_error:
goto io_error
reset_read_channel()
void cbm.CHRIN()
if cbm.READST()!=0
goto io_error
while cbm.CHRIN()!='"' {
; skip up to entry name
}
if cbm.READST()!=0
goto io_error
cx16.r0 = &list_filename
repeat {
@@ -0,0 +1,3 @@
coroutines {
; the VM doesn't support coroutines because the flow of control is radically different than how the 6502 CPU does it.
}
+1
View File
@@ -409,6 +409,7 @@ monogfx {
; Non-recursive scanline flood fill.
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
; Also see https://lodev.org/cgtutor/floodfill.html
word @zp xx = x as word
word @zp yy = y as word
word x1
+10 -2
View File
@@ -190,6 +190,14 @@ sys {
}}
}
sub push_returnaddress(uword w) {
; note: this actually doesn't do anything useful on the VM because the code execution doesn't use the simulated cpu stack
%ir {{
loadm.w r65535,sys.pushw.w
push.w r65535
}}
}
sub pop() -> ubyte {
; note: this *should* be inlined, however since the VM has separate program counter and value stacks, this also works
%ir {{
@@ -233,8 +241,8 @@ sys {
cx16 {
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
; they are simulated on the VirtualMachine as well but their location in memory is different
; the sixteen virtual 16-bit registers that the Commander X16 has defined in the zeropage
; they are on the VirtualMachine as well, but their location in memory is different
&uword r0 = $ff02
&uword r1 = $ff04
&uword r2 = $ff06
+4 -3
View File
@@ -1,8 +1,9 @@
package prog8
import kotlinx.cli.*
import prog8.ast.base.AstException
import prog8.ast.AstException
import prog8.code.core.CbmPrgLauncherType
import prog8.code.source.ImportFileSystem
import prog8.code.target.CompilationTargets
import prog8.code.target.Cx16Target
import prog8.code.target.getCompilationTargetByName
@@ -95,8 +96,8 @@ private fun compileMain(args: Array<String>): Boolean {
return false
}
val srcdirs = sourceDirs.toMutableList()
if(srcdirs.firstOrNull()!=".")
val srcdirs = sourceDirs.map { ImportFileSystem.expandTilde(it) }.toMutableList()
if(sourceDirs.firstOrNull()!=".")
srcdirs.add(0, ".")
if(startVm==null) {
@@ -1,9 +1,9 @@
package prog8.compiler
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.FatalAstException
import prog8.ast.base.SyntaxError
import prog8.ast.AstException
import prog8.ast.FatalAstException
import prog8.ast.SyntaxError
import prog8.ast.expressions.*
import prog8.ast.statements.VarDecl
import prog8.code.core.*
+1 -1
View File
@@ -1,9 +1,9 @@
package prog8.compiler
import com.github.michaelbull.result.onFailure
import prog8.ast.AstException
import prog8.ast.IBuiltinFunctions
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteral
import prog8.ast.printProgram
@@ -3,7 +3,7 @@ package prog8.compiler
import com.github.michaelbull.result.*
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.SyntaxError
import prog8.ast.SyntaxError
import prog8.ast.statements.Directive
import prog8.ast.statements.DirectiveArg
import prog8.code.core.IErrorReporter
@@ -135,7 +135,7 @@ class ModuleImporter(private val program: Program,
if (importingModule == null) { // <=> imported from library module
sourcePaths
} else {
val pathFromImportingModule = (Path(importingModule.position.file).parent ?: Path("")).absolute()
val pathFromImportingModule = (Path(importingModule.position.file).parent ?: Path("")).absolute().normalize()
listOf(pathFromImportingModule) + sourcePaths
}
@@ -1,8 +1,8 @@
package prog8.compiler.astprocessing
import prog8.ast.*
import prog8.ast.base.FatalAstException
import prog8.ast.base.SyntaxError
import prog8.ast.FatalAstException
import prog8.ast.SyntaxError
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
@@ -533,7 +533,7 @@ internal class AstChecker(private val program: Program,
errors.err("identifiers cannot start with an underscore", p.position)
if(p.type.isPassByRef && !p.type.isString && !p.type.isUnsignedByteArray) {
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)
errors.err("this pass-by-reference type can't be used as a parameter type. Instead, use just 'uword' to receive the address, or maybe don't pass the value via a parameter but access it directly.", p.position)
}
}
}
@@ -642,7 +642,7 @@ internal class AstChecker(private val program: Program,
fcallTarget.returntypes.zip(targets).withIndex().forEach { (index, p) ->
val (returnType, target) = p
val targetDt = target.inferType(program).getOrUndef()
if (!target.void && !(returnType isAssignableTo targetDt))
if (!target.void && returnType != targetDt)
errors.err("can't assign returnvalue #${index + 1} to corresponding target; $returnType vs $targetDt", target.position)
}
}
@@ -703,7 +703,12 @@ internal class AstChecker(private val program: Program,
if (variable!=null) {
if (variable.type == VarDeclType.CONST && addressOf.arrayIndex == null)
errors.err("invalid pointer-of operand type",addressOf.position)
if(addressOf.arrayIndex!=null && variable.datatype.isSplitWordArray) {
errors.err("cannot take the adress of a word element that is in a split-word array", addressOf.position)
}
}
super.visit(addressOf)
}
@@ -1467,18 +1472,18 @@ internal class AstChecker(private val program: Program,
}
if(funcName[0] in InplaceModifyingBuiltinFunctions) {
// in-place modification, can't be done on literals
// in-place modification, can be done on specific types of arguments only (variables, array elements)
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", firstArg.position)
errors.err("this function can only act on an identifier or array element", 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)
errors.err("this function can only act on an identifier or array element", thirdArg.position)
if(fourthArg !is IdentifierReference && fourthArg !is ArrayIndexedExpression)
errors.err("invalid argument to a in-place modifying function", fourthArg.position)
errors.err("this function can only act on an identifier or array element", 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)
@@ -3,8 +3,8 @@ package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.base.SyntaxError
import prog8.ast.FatalAstException
import prog8.ast.SyntaxError
import prog8.ast.expressions.*
import prog8.ast.findParentNode
import prog8.ast.statements.*
@@ -198,7 +198,7 @@ class AstPreprocessor(val program: Program,
options.dontSplitWordArrays && decl.datatype.isSplitWordArray
private fun makeUnSplitArray(decl: VarDecl): Iterable<IAstModification> {
val splitDt = DataType.arrayFor(decl.datatype.sub!!.dt, false)
val splitDt = DataType.arrayFor(decl.datatype.sub!!, false)
val newDecl = VarDecl(
decl.type, decl.origin, splitDt, decl.zeropage, decl.splitwordarray, decl.arraysize, decl.name, emptyList(),
decl.value?.copy(), decl.sharedWithAsm, decl.alignment, false, decl.position
@@ -5,7 +5,7 @@ import com.github.michaelbull.result.Result
import com.github.michaelbull.result.getOrElse
import com.github.michaelbull.result.mapError
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.code.ast.*
@@ -15,6 +15,7 @@ import prog8.code.source.SourceCode
import prog8.compiler.builtinFunctionReturnType
import java.io.File
import kotlin.io.path.Path
import kotlin.io.path.absolute
import kotlin.io.path.isRegularFile
@@ -257,10 +258,10 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
val offset: UInt? = if(directive.args.size>=2) directive.args[1].int!! else null
val length: UInt? = if(directive.args.size>=3) directive.args[2].int!! else null
val abspath = if(File(filename).isFile) {
Path(filename).toAbsolutePath()
Path(filename).absolute()
} else {
val sourcePath = Path(directive.definingModule.source.origin)
sourcePath.resolveSibling(filename).toAbsolutePath()
sourcePath.resolveSibling(filename).absolute()
}
if(abspath.toFile().isFile)
PtIncludeBinary(abspath, offset, length, directive.position)
@@ -743,8 +744,8 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
if(type.isSplitWordArray) {
// ranges are never a split word array!
when(type.sub) {
is SubSignedWord -> type = DataType.arrayFor(BaseDataType.WORD, false)
is SubUnsignedWord -> type = DataType.arrayFor(BaseDataType.UWORD, false)
BaseDataType.WORD -> type = DataType.arrayFor(BaseDataType.WORD, false)
BaseDataType.UWORD -> type = DataType.arrayFor(BaseDataType.UWORD, false)
else -> { }
}
}
@@ -94,7 +94,7 @@ private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtPro
val identifier = node.target as? PtIdentifier
if (identifier != null) {
val stNode = st.lookup(identifier.name)!!
val targetSub = stNode.astNode.definingSub()
val targetSub = stNode.astNode!!.definingSub()
if (targetSub != node.definingSub())
jumpsAndCallsToAugment.add(node)
}
@@ -2,7 +2,7 @@ package prog8.compiler.astprocessing
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.base.FatalAstException
import prog8.ast.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
@@ -3,7 +3,7 @@ package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
@@ -264,7 +264,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
}
} else if(targetDt==BaseDataType.BOOL) {
addTypecastOrCastedValueModification(modifications, it.second, BaseDataType.BOOL, call as Node)
} else if(argDt isAssignableTo DataType.forDt(targetDt)) {
} else if(!targetDt.isIterable && argDt isAssignableTo DataType.forDt(targetDt)) {
if(!argDt.isString || targetDt!=BaseDataType.UWORD)
addTypecastOrCastedValueModification(modifications, it.second, targetDt, call as Node)
}
@@ -1,6 +1,6 @@
package prog8.compiler.astprocessing
import prog8.ast.base.FatalAstException
import prog8.ast.FatalAstException
import prog8.code.ast.PtExpression
import prog8.code.ast.PtFunctionCall
import prog8.code.ast.PtTypeCast
@@ -4,7 +4,7 @@ import prog8.ast.IFunctionCall
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
+2 -2
View File
@@ -61,7 +61,7 @@ private fun prepareTestFiles(source: String, optimize: Boolean, target: ICompila
}
val filepath = searchIn.asSequence()
.map { it.resolve("$source.p8") }
.map { it.normalize().absolute() }
.map { it.absolute().normalize() }
.map { workingDir.relativize(it) }
.first { it.exists() }
val displayName = "${examplesDir.relativize(filepath.absolute())}: ${target.name}, optimize=$optimize"
@@ -172,7 +172,6 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
listOf(
"animals",
"balls",
"coroutines",
"cube3d",
"cube3d-float",
"cube3d-gfx",
@@ -182,6 +181,7 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
"maze",
"mandelbrot",
"mandelbrot-gfx",
"multitasking",
"numbergame",
"primes",
"queens",
@@ -11,6 +11,7 @@ import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Label
import prog8.code.target.Cx16Target
import prog8tests.helpers.*
import kotlin.io.path.absolute
import kotlin.io.path.name
@@ -97,7 +98,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
// the bug we're testing for (#54) was hidden if outputDir == workingDir
withClue("sanity check: workingDir and outputDir should not be the same folder") {
outputDir.normalize().toAbsolutePath() shouldNotBe workingDir.normalize().toAbsolutePath()
outputDir.absolute().normalize() shouldNotBe workingDir.absolute().normalize()
}
withClue("argument to assembler directive .binary " +
+2
View File
@@ -23,6 +23,8 @@ class TestLaunchEmu: FunSpec({
</VARIABLESNOINIT>
<VARIABLESWITHINIT>
</VARIABLESWITHINIT>
<CONSTANTS>
</CONSTANTS>
<MEMORYMAPPEDVARIABLES>
</MEMORYMAPPEDVARIABLES>
+14 -19
View File
@@ -253,32 +253,27 @@ class TestMemory: FunSpec({
}
context("memsizer") {
withData(VMTarget(), AtariTarget(), C64Target(), PETTarget(), AtariTarget(), C128Target()) { target ->
shouldThrow<NoSuchElementException> {
target.memorySize(SubType.forDt(BaseDataType.UNDEFINED))
withData(VMTarget(), AtariTarget(), C64Target(), PETTarget(), AtariTarget(), C128Target(), Neo6502Target()) { target ->
shouldThrow<IllegalArgumentException> {
target.memorySize(BaseDataType.UNDEFINED)
}
shouldThrow<NoSuchElementException> {
target.memorySize(SubType.forDt(BaseDataType.LONG))
shouldThrow<IllegalArgumentException> {
target.memorySize(BaseDataType.LONG)
}
shouldThrow<NoSuchElementException> {
target.memorySize(SubType.forDt(BaseDataType.STR))
}
shouldThrow<NoSuchElementException> {
target.memorySize(SubType.forDt(BaseDataType.ARRAY))
}
shouldThrow<NoSuchElementException> {
target.memorySize(SubType.forDt(BaseDataType.ARRAY_SPLITW))
}
target.memorySize(SubType.forDt(BaseDataType.BOOL)) shouldBe 1
target.memorySize(SubType.forDt(BaseDataType.BYTE)) shouldBe 1
target.memorySize(SubType.forDt(BaseDataType.WORD)) shouldBe 2
target.memorySize(SubType.forDt(BaseDataType.FLOAT)) shouldBe target.machine.FLOAT_MEM_SIZE
target.memorySize(BaseDataType.BOOL) shouldBe 1
target.memorySize(BaseDataType.BYTE) shouldBe 1
target.memorySize(BaseDataType.WORD) shouldBe 2
target.memorySize(BaseDataType.FLOAT) shouldBe target.machine.FLOAT_MEM_SIZE
target.memorySize(DataType.forDt(BaseDataType.UNDEFINED), null) shouldBe 2
target.memorySize(DataType.forDt(BaseDataType.BOOL), null) shouldBe 1
target.memorySize(DataType.forDt(BaseDataType.WORD), null) shouldBe 2
target.memorySize(DataType.forDt(BaseDataType.FLOAT), null) shouldBe target.machine.FLOAT_MEM_SIZE
target.memorySize(DataType.forDt(BaseDataType.STR), null) shouldBe 2
target.memorySize(DataType.forDt(BaseDataType.STR), 50) shouldBe 50
target.memorySize(BaseDataType.STR) shouldBe 2
target.memorySize(BaseDataType.ARRAY) shouldBe 2
target.memorySize(BaseDataType.ARRAY_SPLITW) shouldBe 2
target.memorySize(DataType.arrayFor(BaseDataType.BOOL), 10) shouldBe 10
target.memorySize(DataType.arrayFor(BaseDataType.BYTE), 10) shouldBe 10
+1 -1
View File
@@ -5,7 +5,7 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.ast.base.ExpressionError
import prog8.ast.ExpressionError
import prog8.ast.expressions.ArrayLiteral
import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteral
+1 -1
View File
@@ -21,7 +21,7 @@ class TestSymbolTable: FunSpec({
st.type shouldBe StNodeType.GLOBAL
st.children shouldBe mutableMapOf()
st.astNode shouldBeSameInstanceAs astNode
st.astNode.position shouldBe Position.DUMMY
st.astNode!!.position shouldBe Position.DUMMY
}
test("symboltable flatten") {
+61
View File
@@ -383,4 +383,65 @@ main {
errors.warnings.size shouldBe 0
errors.errors[0] shouldContain "missing &"
}
test("can't use uword[] as a parameter type give clear error") {
val src="""
main {
sub start() {
uword[] @nosplit array = [1111,2222]
funcw1(array)
funcw1([1111,2222])
funcw2(array)
funcw2([1111,2222])
funcb([11,22])
}
sub funcw1(uword[] ptr) {
; error
}
sub funcw2(uword ptr) {
; ok
}
sub funcb(ubyte[] ptr) {
; ok
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, src, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 3
errors.warnings.size shouldBe 0
errors.errors[0] shouldContain ":5:16: argument 1 type mismatch"
errors.errors[1] shouldContain ":6:16: argument 1 type mismatch"
errors.errors[2] shouldContain ":12:16: this pass-by-reference type can't be used as a parameter type"
}
test("proper type checking for multi-value assigns") {
val src="""
main {
sub start() {
bool bb
ubyte ub
uword uw
uw, void = thing2()
uw, bb = thing2()
uw, ub = thing2()
}
asmsub thing2() -> ubyte @A, bool @Pc {
%asm {{
rts
}}
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, src, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 4
errors.errors[0] shouldContain "can't assign returnvalue #1 to corresponding target; ubyte vs uword"
errors.errors[1] shouldContain "can't assign returnvalue #1 to corresponding target; ubyte vs uword"
errors.errors[2] shouldContain "can't assign returnvalue #1 to corresponding target; ubyte vs uword"
errors.errors[3] shouldContain "can't assign returnvalue #2 to corresponding target; bool vs ubyte"
}
})
+1 -1
View File
@@ -103,7 +103,7 @@ class TestProg8Parser: FunSpec( {
val nlUnix = "\n"
val nlMac = "\r"
//parseModule(Paths.get("test", "fixtures", "mac_newlines.p8").toAbsolutePath())
//parseModule(Paths.get("test", "fixtures", "mac_newlines.p8").absolute())
// a good mix of all kinds of newlines:
val srcText =
+13 -29
View File
@@ -15,7 +15,6 @@ import prog8.code.ast.*
import prog8.code.core.BaseDataType
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.core.SubType
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
@@ -846,29 +845,7 @@ main {
errors.errors[2] shouldEndWith "cannot assign to 'void', perhaps a void function call was intended"
}
test("datatype subtype consistencies") {
shouldThrow<NoSuchElementException> {
SubType.forDt(BaseDataType.STR)
}
shouldThrow<NoSuchElementException> {
SubType.forDt(BaseDataType.UNDEFINED)
}
shouldThrow<NoSuchElementException> {
SubType.forDt(BaseDataType.ARRAY_SPLITW)
}
shouldThrow<NoSuchElementException> {
SubType.forDt(BaseDataType.ARRAY)
}
SubType.forDt(BaseDataType.FLOAT).dt shouldBe BaseDataType.FLOAT
}
test("datatype consistencies") {
shouldThrow<NoSuchElementException> {
DataType.forDt(BaseDataType.ARRAY)
}
shouldThrow<NoSuchElementException> {
DataType.forDt(BaseDataType.ARRAY_SPLITW)
}
DataType.forDt(BaseDataType.UNDEFINED).isUndefined shouldBe true
DataType.forDt(BaseDataType.LONG).isLong shouldBe true
DataType.forDt(BaseDataType.WORD).isWord shouldBe true
@@ -876,6 +853,19 @@ main {
DataType.forDt(BaseDataType.BYTE).isByte shouldBe true
DataType.forDt(BaseDataType.UBYTE).isByte shouldBe true
DataType.arrayFor(BaseDataType.UBYTE, true).isUnsignedByteArray shouldBe true
DataType.arrayFor(BaseDataType.FLOAT).isFloatArray shouldBe true
DataType.arrayFor(BaseDataType.UWORD).isUnsignedWordArray shouldBe true
DataType.arrayFor(BaseDataType.UWORD).isArray shouldBe true
DataType.arrayFor(BaseDataType.UWORD).isSplitWordArray shouldBe true
DataType.arrayFor(BaseDataType.UWORD, false).isSplitWordArray shouldBe false
shouldThrow<NoSuchElementException> {
DataType.forDt(BaseDataType.ARRAY)
}
shouldThrow<NoSuchElementException> {
DataType.forDt(BaseDataType.ARRAY_SPLITW)
}
shouldThrow<NoSuchElementException> {
DataType.arrayFor(BaseDataType.ARRAY)
}
@@ -885,12 +875,6 @@ main {
shouldThrow<NoSuchElementException> {
DataType.arrayFor(BaseDataType.UNDEFINED)
}
DataType.arrayFor(BaseDataType.UBYTE, true).isUnsignedByteArray shouldBe true
DataType.arrayFor(BaseDataType.FLOAT).isFloatArray shouldBe true
DataType.arrayFor(BaseDataType.UWORD).isUnsignedWordArray shouldBe true
DataType.arrayFor(BaseDataType.UWORD).isArray shouldBe true
DataType.arrayFor(BaseDataType.UWORD).isSplitWordArray shouldBe true
DataType.arrayFor(BaseDataType.UWORD, false).isSplitWordArray shouldBe false
}
test("array of strings becomes array of uword pointers") {
@@ -353,6 +353,7 @@ main {
sub start() {
sys.push(11)
sys.pushw(2222)
sys.push_returnaddress(3333)
cx16.r2++
cx16.r1 = sys.popw()
cx16.r0L = sys.pop()
@@ -363,6 +364,7 @@ main {
val assembly = assemblyFile.readText()
assembly shouldContain "inlined routine follows: push"
assembly shouldContain "inlined routine follows: pushw"
assembly shouldContain "inlined routine follows: push_returnaddress"
assembly shouldContain "inlined routine follows: pop"
assembly shouldContain "inlined routine follows: popw"
}
+4 -4
View File
@@ -24,7 +24,7 @@ internal object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray || dt.isSplitWordArray) {
require(numElements!=null)
return when(dt.sub?.dt) {
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
BaseDataType.FLOAT -> numElements*5
@@ -38,8 +38,8 @@ internal object DummyMemsizer : IMemSizer {
}
}
override fun memorySize(dt: SubType): Int {
return memorySize(DataType.forDt(dt.dt), null)
override fun memorySize(dt: BaseDataType): Int {
return memorySize(DataType.forDt(dt), null)
}
}
@@ -82,7 +82,7 @@ internal object DummyCompilationTarget : ICompilationTarget {
throw NotImplementedError("dummy")
}
override fun memorySize(dt: SubType): Int {
override fun memorySize(dt: BaseDataType): Int {
throw NotImplementedError("dummy")
}
}
-3
View File
@@ -1,6 +1,5 @@
package prog8.ast
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.InferredTypes
@@ -385,6 +384,4 @@ fun defaultZero(dt: BaseDataType, position: Position) = when(dt) {
else -> throw FatalAstException("can only determine default zero value for a numeric type")
}
fun defaultZero(dt: SubType, position: Position) = defaultZero(dt.dt, position)
fun defaultZero(idt: InferredTypes.InferredType, position: Position) = defaultZero(idt.getOrUndef().base, position)
@@ -1,4 +1,4 @@
package prog8.ast.base
package prog8.ast
import prog8.ast.expressions.IdentifierReference
import prog8.code.core.Position
-1
View File
@@ -1,6 +1,5 @@
package prog8.ast
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.StringLiteral
import prog8.ast.statements.*
@@ -3,8 +3,8 @@ package prog8.ast.antlr
import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.Token
import org.antlr.v4.runtime.tree.TerminalNode
import prog8.ast.base.FatalAstException
import prog8.ast.base.SyntaxError
import prog8.ast.FatalAstException
import prog8.ast.SyntaxError
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.code.core.*
@@ -1,10 +1,10 @@
package prog8.ast.expressions
import prog8.ast.ExpressionError
import prog8.ast.FatalAstException
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ExpressionError
import prog8.ast.base.FatalAstException
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
@@ -83,7 +83,7 @@ object InferredTypes {
type.isString -> InferredType.known(BaseDataType.STR)
type.isLong -> InferredType.known(BaseDataType.LONG)
type.isSplitWordArray -> {
when(type.sub?.dt) {
when(type.sub) {
BaseDataType.UWORD -> InferredType.known(DataType.arrayFor(BaseDataType.UWORD, true))
BaseDataType.WORD -> InferredType.known(DataType.arrayFor(BaseDataType.WORD, true))
BaseDataType.STR -> InferredType.known(DataType.arrayFor(BaseDataType.STR, true))
@@ -91,7 +91,7 @@ object InferredTypes {
}
}
type.isArray -> {
InferredType.known(DataType.arrayFor(type.sub!!.dt, false))
InferredType.known(DataType.arrayFor(type.sub!!, false))
}
else -> throw IllegalArgumentException("invalid type")
}
@@ -1,7 +1,6 @@
package prog8.ast.statements
import prog8.ast.*
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
@@ -285,8 +284,8 @@ class VarDecl(val type: VarDeclType,
if(arrayDt.isSplitWordArray) {
// autovars for array literals are NOT stored as a split word array!
when(arrayDt.sub) {
is SubSignedWord -> arrayDt = DataType.arrayFor(BaseDataType.WORD, false)
is SubUnsignedWord -> arrayDt = DataType.arrayFor(BaseDataType.UWORD, false)
BaseDataType.WORD -> arrayDt = DataType.arrayFor(BaseDataType.WORD, false)
BaseDataType.UWORD -> arrayDt = DataType.arrayFor(BaseDataType.UWORD, false)
else -> { }
}
}
@@ -1,7 +1,6 @@
package prog8.ast.walk
import prog8.ast.*
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.code.core.AssociativeOperators
+1
View File
@@ -2,6 +2,7 @@
%import buffers
%import compression
%import coroutines
%import conv
%import cx16logo
%import math
+1
View File
@@ -3,6 +3,7 @@
%import buffers
%import compression
%import conv
%import coroutines
%import cx16logo
%import diskio
;;%import floats
+1
View File
@@ -3,6 +3,7 @@
%import buffers
%import compression
%import conv
%import coroutines
%import cx16logo
%import diskio
%import floats
+1
View File
@@ -4,6 +4,7 @@
%import bmx
%import compression
%import conv
%import coroutines
%import cx16logo
%import diskio
%import emudbg
+1
View File
@@ -3,6 +3,7 @@
%import buffers
%import compression
%import conv
%import coroutines
%import math
%import prog8_lib
%import strings
+1
View File
@@ -3,6 +3,7 @@
%import buffers
%import compression
%import conv
%import coroutines
%import cx16logo
%import math
%import prog8_lib
@@ -1,5 +1,5 @@
Prog8 compiler v11.0 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.0.1 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-atari.p8
@@ -102,11 +102,31 @@ conv {
}
LIBRARY MODULE NAME: coroutines
-------------------------------
coroutines {
const ubyte MAX_TASKS
ubyte active_task
uword[] returnaddresses
uword supervisor
uword[] tasklist
uword[] userdatas
add (uword taskaddress, uword userdata) -> ubyte
current () -> ubyte
kill (ubyte taskid)
killall ()
run (uword supervisor_routine)
termination ()
yield () -> uword
}
LIBRARY MODULE NAME: cx16logo
-----------------------------
cx16logo {
@nosplit uword[] logo_lines
uword[] logo_lines
logo ()
logo_at (ubyte column, ubyte row)
}
@@ -242,6 +262,7 @@ sys {
progend () -> uword @AY
progstart () -> uword @AY
push (ubyte value @A)
push_returnaddress (uword address @XY)
pushw (uword value @AY)
read_flags () -> ubyte @A
reset_system ()
@@ -1,5 +1,5 @@
Prog8 compiler v11.0 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.0.1 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-c128.p8
@@ -102,11 +102,31 @@ conv {
}
LIBRARY MODULE NAME: coroutines
-------------------------------
coroutines {
const ubyte MAX_TASKS
ubyte active_task
uword[] returnaddresses
uword supervisor
uword[] tasklist
uword[] userdatas
add (uword taskaddress, uword userdata) -> ubyte
current () -> ubyte
kill (ubyte taskid)
killall ()
run (uword supervisor_routine)
termination ()
yield () -> uword
}
LIBRARY MODULE NAME: cx16logo
-----------------------------
cx16logo {
@nosplit uword[] logo_lines
uword[] logo_lines
logo ()
logo_at (ubyte column, ubyte row)
}
@@ -300,7 +320,7 @@ cbm {
MEMBOT (uword address @XY, bool dir @Pc) -> uword @XY = $ff9c
MEMTOP (uword address @XY, bool dir @Pc) -> uword @XY = $ff99
OPEN () -> clobbers (X,Y) -> bool @Pc, ubyte @A = $ffc0
PLOT (ubyte col @Y, ubyte row @X, bool dir @Pc) -> clobbers (A) -> ubyte @X, ubyte @Y = $fff0
PLOT (ubyte col @Y, ubyte row @X, bool dir @Pc) -> clobbers (A) -> ubyte @Y, ubyte @X = $fff0
RAMTAS () -> clobbers (A,X,Y) = $ff87
RDTIM () -> ubyte @A, ubyte @X, ubyte @Y = $ffde
RDTIM16 () -> clobbers (X) -> uword @AY
@@ -507,6 +527,7 @@ sys {
progend () -> uword @AY
progstart () -> uword @AY
push (ubyte value @A)
push_returnaddress (uword address @XY)
pushw (uword value @AY)
read_flags () -> ubyte @A
reset_system ()
@@ -1,5 +1,5 @@
Prog8 compiler v11.0 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.0.1 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-c64.p8
@@ -102,11 +102,31 @@ conv {
}
LIBRARY MODULE NAME: coroutines
-------------------------------
coroutines {
const ubyte MAX_TASKS
ubyte active_task
uword[] returnaddresses
uword supervisor
uword[] tasklist
uword[] userdatas
add (uword taskaddress, uword userdata) -> ubyte
current () -> ubyte
kill (ubyte taskid)
killall ()
run (uword supervisor_routine)
termination ()
yield () -> uword
}
LIBRARY MODULE NAME: cx16logo
-----------------------------
cx16logo {
@nosplit uword[] logo_lines
uword[] logo_lines
logo ()
logo_at (ubyte column, ubyte row)
}
@@ -429,7 +449,7 @@ cbm {
MEMBOT (uword address @XY, bool dir @Pc) -> uword @XY = $ff9c
MEMTOP (uword address @XY, bool dir @Pc) -> uword @XY = $ff99
OPEN () -> clobbers (X,Y) -> bool @Pc, ubyte @A = $ffc0
PLOT (ubyte col @Y, ubyte row @X, bool dir @Pc) -> clobbers (A) -> ubyte @X, ubyte @Y = $fff0
PLOT (ubyte col @Y, ubyte row @X, bool dir @Pc) -> clobbers (A) -> ubyte @Y, ubyte @X = $fff0
RAMTAS () -> clobbers (A,X,Y) = $ff87
RDTIM () -> ubyte @A, ubyte @X, ubyte @Y = $ffde
RDTIM16 () -> clobbers (X) -> uword @AY
@@ -633,6 +653,7 @@ sys {
progend () -> uword @AY
progstart () -> uword @AY
push (ubyte value @A)
push_returnaddress (uword address @XY)
pushw (uword value @AY)
read_flags () -> ubyte @A
reset_system ()
@@ -1,5 +1,5 @@
Prog8 compiler v11.0 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.0.1 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-cx16.p8
@@ -140,11 +140,31 @@ conv {
}
LIBRARY MODULE NAME: coroutines
-------------------------------
coroutines {
const ubyte MAX_TASKS
ubyte active_task
uword[] returnaddresses
uword supervisor
uword[] tasklist
uword[] userdatas
add (uword taskaddress, uword userdata) -> ubyte
current () -> ubyte
kill (ubyte taskid)
killall ()
run (uword supervisor_routine)
termination ()
yield () -> uword
}
LIBRARY MODULE NAME: cx16logo
-----------------------------
cx16logo {
@nosplit uword[] logo_lines
uword[] logo_lines
logo ()
logo_at (ubyte column, ubyte row)
}
@@ -713,7 +733,7 @@ cbm {
MEMBOT (uword address @XY, bool dir @Pc) -> uword @XY = $ff9c
MEMTOP (uword address @XY, bool dir @Pc) -> uword @XY, ubyte @A = $ff99
OPEN () -> clobbers (X,Y) -> bool @Pc, ubyte @A = $ffc0
PLOT (ubyte col @Y, ubyte row @X, bool dir @Pc) -> clobbers (A) -> ubyte @X, ubyte @Y = $fff0
PLOT (ubyte col @Y, ubyte row @X, bool dir @Pc) -> clobbers (A) -> ubyte @Y, ubyte @X = $fff0
RAMTAS () -> clobbers (A,X,Y) = $ff87
RDTIM () -> ubyte @A, ubyte @X, ubyte @Y = $ffde
RDTIM16 () -> clobbers (X) -> uword @AY
@@ -1186,6 +1206,7 @@ sys {
progend () -> uword @AY
progstart () -> uword @AY
push (ubyte value @A)
push_returnaddress (uword address @XY)
pushw (uword value @AY)
read_flags () -> ubyte @A
reset_system ()
@@ -1277,6 +1298,7 @@ txt {
setchr (ubyte col @X, ubyte row @Y, ubyte character @A) -> clobbers (A)
setclr (ubyte col @X, ubyte row @Y, ubyte color @A) -> clobbers (A)
spc ()
t256c (bool enable)
uppercase ()
waitkey () -> ubyte @A
width () -> clobbers (X,Y) -> ubyte @A
@@ -1,5 +1,5 @@
Prog8 compiler v11.0 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.0.1 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-neo.p8
@@ -102,6 +102,26 @@ conv {
}
LIBRARY MODULE NAME: coroutines
-------------------------------
coroutines {
const ubyte MAX_TASKS
ubyte active_task
uword[] returnaddresses
uword supervisor
uword[] tasklist
uword[] userdatas
add (uword taskaddress, uword userdata) -> ubyte
current () -> ubyte
kill (ubyte taskid)
killall ()
run (uword supervisor_routine)
termination ()
yield () -> uword
}
LIBRARY MODULE NAME: math
-------------------------
@@ -226,6 +246,7 @@ sys {
progend () -> uword @AY
progstart () -> uword @AY
push (ubyte value @A)
push_returnaddress (uword address @XY)
pushw (uword value @AY)
read_flags () -> ubyte @A
reset_system ()
@@ -1,5 +1,5 @@
Prog8 compiler v11.0 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.0.1 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-pet32.p8
@@ -102,11 +102,31 @@ conv {
}
LIBRARY MODULE NAME: coroutines
-------------------------------
coroutines {
const ubyte MAX_TASKS
ubyte active_task
uword[] returnaddresses
uword supervisor
uword[] tasklist
uword[] userdatas
add (uword taskaddress, uword userdata) -> ubyte
current () -> ubyte
kill (ubyte taskid)
killall ()
run (uword supervisor_routine)
termination ()
yield () -> uword
}
LIBRARY MODULE NAME: cx16logo
-----------------------------
cx16logo {
@nosplit uword[] logo_lines
uword[] logo_lines
logo ()
logo_at (ubyte column, ubyte row)
}
@@ -260,6 +280,7 @@ sys {
progend () -> uword @AY
progstart () -> uword @AY
push (ubyte value @A)
push_returnaddress (uword address @XY)
pushw (uword value @AY)
read_flags () -> ubyte @A
reset_system ()
@@ -1,5 +1,5 @@
Prog8 compiler v11.0 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.0.1 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-virtual.p8
@@ -99,7 +99,7 @@ LIBRARY MODULE NAME: cx16logo
-----------------------------
cx16logo {
@nosplit uword[] logo_lines
uword[] logo_lines
logo ()
logo_at (ubyte column, ubyte row)
}
@@ -344,6 +344,7 @@ sys {
pop () -> ubyte
popw () -> uword
push (ubyte b)
push_returnaddress (uword w)
pushw (uword w)
read_flags () -> ubyte
reset_system ()
+2 -2
View File
@@ -73,8 +73,8 @@ Subroutines
variables declared in their parent subroutine(s).
- Everything in prog8 is publicly accessible from everywhere else (via fully scoped names) - there is no notion of private or public symbol accessibility.
- Because there is no callstack for subroutine arguments, it becomes very easy to manipulate the return address that *does* get pushed on the stack by the cpu.
With only a little bit of code it is possible to implement a simple cooperative multitasking system that runs multiple tasks simultaneously. See the "coroutines" example.
Each task is a subroutine and it simply has its state stored in its statically allocated variables so it can resume after a yield, without doing anything special.
With only a little bit of code it is possible to implement a simple cooperative multitasking system that runs multiple tasks simultaneously. See the "multitasking" example,
which uses the "coroutines" library. Each task is a subroutine and it simply has its state stored in the statically allocated variables so it can resume after yielding, without doing anything special.
Pointers
--------
+1 -1
View File
@@ -102,7 +102,7 @@ Features
- Several specialized built-in functions, such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
- Various powerful built-in libraries to do I/O, number conversions, graphics and more
- Floating point math is supported on certain compiler targets.
- Easy and highly efficient integration with external subroutines and ROM routines on the target systems.
- Provides access to most Kernal ROM routines as external subroutine definitions you can call normally.
- Strings can contain escaped characters but also many symbols directly if they have a PETSCII equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \\, {, } and | are also accepted and converted to the closest PETSCII equivalents.
- Encode strings and characters into petscii or screencodes or even other encodings, as desired (C64/Cx16)
- Automatic ROM/RAM bank switching on certain compiler targets when calling routines in other banks
+39 -8
View File
@@ -221,13 +221,6 @@ callfar2 (bank, address, argA, argX, argY, argCarry) -> uword
Identical to ``callfar``, except here you can give arguments not only for AY,
but for each of the A, X and Y registers (each an ubyte) and the Carry status bit as well (a boolean).
syscall (callnr), syscall1 (callnr, arg), syscall2 (callnr, arg1, arg2), syscall3 (callnr, arg1, arg2, arg3)
Functions for doing a system call on targets that support this. Currently no actual target
uses this though except, possibly, the experimental code generation target!
The regular 6502 based compiler targets just use a subroutine call to asmsub Kernal routines at
specific memory locations. So these builtin function calls are not useful yet except for
experimentation in new code generation targets.
rsave
Saves all registers including status (or only X) on the stack
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* saved,
@@ -369,6 +362,38 @@ Read the `conv source code <https://github.com/irmen/prog8/tree/master/compiler/
to see what's in there.
coroutines (experimental)
-------------------------
Provides a system to make cooperative multitasking programs via coroutines.
A 'coroutine' is a subroutine whose execution you can pause and resume.
This library handles the voodoo for you to switch between such coroutines transparently,
so it can seem that your program is executing many subroutines at the same time.
API is experimental and may change or disappear in a future version.
Read the `coroutines source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/coroutines.p8>`_
to see what's in there. And look at the ``multitasking`` example to see how it can be used.
Here is a minimal example (if the library gets more stable, better docs will be written here)::
%import coroutine
main {
sub start() {
coroutines.killall()
coroutines.add(&some_task, 1111)
; ... add more tasks here or later
coroutines.run(0)
}
sub some_task() {
repeat 100 {
uword userdata = coroutines.yield()
; ... do something...
}
}
}
cx16
----
This is available on *all targets*, it is always imported as part of syslib.
@@ -1079,13 +1104,19 @@ sys (part of syslib)
``pushw (value)``
pushes a 16-bit word value on the CPU hardware stack. Low-level function that should normally not be used.
Don't assume anything about the order in which the bytes are pushed - popw will make sense of them again.
``push_returnaddress (address)``
pushes a 16 bit memory address on the CPU hardware stack in the same byte order as a JSR instruction would,
which means the next RTS instruction will jump to that address instead.you
You cannot use pushw() for this because the bytes pushed by JSR are different
``pop ()``
pops a byte value off the CPU hardware stack and returns it.
Low-level function that should normally not be used.
``popw ()``
pops a 16-bit word value off the CPU hardware stack and returns it.
pops a 16-bit word value off the CPU hardware stack that was pushed before by pushw, and returns it.
Low-level function that should normally not be used.
+12 -7
View File
@@ -127,18 +127,23 @@ So the normal IRQ vector can still run and will be when the program is started!
CPU
===
Directly Usable Registers
-------------------------
Directly Accessible Registers
-----------------------------
The hardware CPU registers are not directly accessible from regular Prog8 code.
If you need to mess with them, you'll have to use inline assembly.
The hardware CPU registers (A, X, Y) are not directly accessible from regular Prog8 code.
If you need to work with them, you'll have to use some inline assembly with ``%asm``.
Or, if they are required to have a value as arguments to some external kernal or library assembly routine,
just use a normal subroutine call to an ``extsub`` that correctly specifies what registers go where.
The compiler will then take care of loading the arguments into the required registers and returning
any response value(s) back to the prog8 code.
The status register (P) carry flag and interrupt disable flag can be written via a couple of special
The status register (P) carry flag and interrupt disable flag *can* be written via a couple of special
builtin functions (``set_carry()``, ``clear_carry()``, ``set_irqd()``, ``clear_irqd()``),
and read via the ``read_flags()`` function.
and read via the ``read_flags()`` function. With the special status branch statements like ``if_cc``,
``if_cs`` etc you can branch directly on the status of the flags.
The 16 'virtual' 16-bit registers that are defined on the Commander X16 machine are not real hardware
registers and are just 16 memory-mapped word values that you *can* access directly.
registers and are just 16 memory-mapped word values that you *can* access directly from everywhere.
IRQ Handling
+16 -3
View File
@@ -1,6 +1,7 @@
TODO
====
- add paypal donation button as well?
- announce prog8 on the 6502.org site?
...
@@ -9,8 +10,19 @@ TODO
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
- why is this code so much larger with charpos variable, than with just using cx16.r0L ?
ubyte charpos
for charpos in DIALOG_TOPX+1 to DIALOG_TOPX+30 {
txt.setchr(charpos, cx16.r1L, txt.getchr(charpos, cx16.r1L) ^ 128)
}
- support &, &< and &> on array elements from split word arrays too not just the array as a whole (to get rid of the error "&< is only valid on array variables"
and "cannot take the adress of a word element that is in a split-word array" and the TODOS "address of element of a split word array")
- after that: fix leftover asmgen split word array todo's
- Kotlin: can we use inline value classes in certain spots?
- Improve the SublimeText syntax file for prog8, you can also install this for 'bat': https://github.com/sharkdp/bat?tab=readme-ov-file#adding-new-syntaxes--language-definitions
- don't do BIT instruction tests via makeBittestCall() fake builtin function. Just do it in the code generator when it encounters the correct bitwise and sequence. (also IR)
- Compiling Libraries: improve ability to create library files in prog8; for instance there's still stuff injected into the start of the start() routine AND there is separate setup logic going on before calling it.
Make up our mind! Maybe all setup does need to be put into start() ? because the program cannot function correctly when the variables aren't initialized properly bss is not cleared etc. etc.
Add a -library $xxxx command line option (and/or some directive) to preselect every setting that is required to make a library at $xxxx rather than a normal loadable and runnable program?
@@ -20,11 +32,11 @@ Future Things and Ideas
- [problematic due to using 64tass:] better support for building library programs, where unused .proc are NOT deleted from the assembly.
Perhaps replace all uses of .proc/.pend/.endproc by .block/.bend will fix that with a compiler flag?
But all library code written in asm uses .proc already..... (textual search/replace when writing the actual asm?)
Maybe propose a patch to 64tass itself that will treat .proc as .block ?
Once new codegen is written that is based on the IR, this point is mostly moot anyway as that will have its own dead code removal on the IR level.
- Allow normal subroutines to return multiple values as well (just as asmsubs already can)
- Change scoping rules for qualified symbols so that they don't always start from the root but behave like other programming languages (look in local scope first)
- Fix missing cases where regular & has to return the start of the split array in memory whatever byte comes first. Search TODO("address of split word array")
- something to reduce the need to use fully qualified names all the time. 'with' ? Or 'using <prefix>'?
- Improve register load order in subroutine call args assignments:
in certain situations (need examples!), the "wrong" order of evaluation of function call arguments is done which results
@@ -43,8 +55,8 @@ Future Things and Ideas
IR/VM
-----
- ExpressionCodeResult: get rid of the separation between single result register and multiple result registers?
- constants are not retained in the IR file, they should. (need to be able to make asm labels from them eventually)
- fix TODO("IR rol/ror on split words array")
- fix "<< in array" / ">> in array"
- implement missing operators in AssignmentGen (array shifts etc)
- support %align on code chunks
- fix call() return value handling
@@ -61,6 +73,7 @@ IR/VM
- getting it in shape for code generation...
- make optimizeBitTest work for IR too to use the BIT instruction?
- make sure that a 6502 codegen based off the IR, still generates BIT instructions when testing bit 7 or 6 of a byte var.
- ExpressionCodeResult: get rid of the separation between single result register and multiple result registers? maybe not, this requires hundreds of lines to change
Libraries
+7 -4
View File
@@ -168,7 +168,7 @@ type identifier type storage size example var declara
``uword[]`` unsigned word array depends on value ``uword[] myvar = [1, 2, 3, 4]``
``float[]`` floating-point array depends on value ``float[] myvar = [1.1, 2.2, 3.3, 4.4]``
``bool[]`` boolean array depends on value ``bool[] myvar = [true, false, true]`` note: consider using bit flags in a byte or word instead to save space
``str[]`` array with string ptrs 2*x bytes + strs ``str[] names = ["ally", "pete"]``
``str[]`` array with string ptrs 2*x bytes + strs ``str[] names = ["ally", "pete"]`` note: equivalent to a uword array.
``str`` string (PETSCII) varies ``str myvar = "hello."``
implicitly terminated by a 0-byte
=============== ======================= ================= =========================================
@@ -326,11 +326,14 @@ Unlike array variables, negative indexing for pointer variables does *not* mean
Instead, it simply addresses memory that lies *before* the pointer variable.
See also :ref:`pointervars`
**LSB/MSB split word arrays:**
**LSB/MSB split word and str arrays:**
As an optimization, (u)word arrays are split by the compiler in memory as two separate arrays,
As an optimization, (u)word arrays and str arrays are split by the compiler in memory as two separate arrays,
one with the LSBs and one with the MSBs of the word values. This is more efficient to access by the 6502 cpu.
It also enables a maximum length of 256 for word arrays, where normally it would have been 128.
It also allows a maximum length of 256 for word arrays, where normally it would have been 128.
For normal prog8 array indexing, the compiler takes care of the distiction for you under water.
*But for assembly code, or code that otherwise accesses the array elements directly, you have to be aware of the distinction from 'normal' arrays.*
In the assembly code, the array is generated as two byte arrays namely ``name_lsb`` and ``name_msb``, immediately following eachother in memory.
The ``@split`` tag can be added to the variable declaration to *always* split the array even when the command line option -dontsplitarrays is set
-140
View File
@@ -1,140 +0,0 @@
; Cooperative multitasking / Coroutines
;
; Uses cpu stack return address juggling to cycle between the different tasks when they call yield().
; Super simplistic implementation here to just show the core idea.
;
; To make it actually useful, we probably have to:
; - make the list of tasks dynamic;
; - allow tasks to finish, allow new tasks to be added
; - return a unique value (pointer that you had to provide when adding the task to the list?)
; from yield() that the subroutine could use to access unique state,
; because right now a single task == a single subroutine; right now you cannot re-use a subroutine to run
; the same task multiple times for different things.
%import textio
main {
sub start() {
txt.print("cooperative multitasking / coroutines\n\n")
txt.print("here are 4 subroutines that each run\nan endless loop bouncing a digit around.")
coroutines.start()
}
}
coroutines {
uword[] tasklist = [&task1, &task2, &task3, &task4, &vsynctask]
sub start() {
goto tasklist[0]
}
uword[len(tasklist)] returnaddresses
ubyte active_task
sub yield() {
; store the return address of the yielding task,
; and continue with the next one instead (round-robin)
returnaddresses[active_task] = sys.popw()
active_task++
if active_task==len(returnaddresses)
active_task=0
cx16.r0 = returnaddresses[active_task]
if cx16.r0==0 {
; fetch start address of next task.
; address on the stack must be pushed in reverse byte order
; also, subtract 1 from the start address because JSR pushes returnaddress minus 1
cx16.r0 = tasklist[active_task]-1
sys.push(cx16.r0H)
sys.push(cx16.r0L)
} else
sys.pushw(cx16.r0)
; returning from yield then continues with the next coroutine
}
sub task1() {
const ubyte x = 5
ubyte y
repeat {
for y in 10 to 24 {
txt.setchr(x, y-1, sc:' ')
txt.setchr(x, y, sc:'1')
yield()
}
for y in 24 downto 10 {
txt.setchr(x, y+1, sc:' ')
txt.setchr(x, y, sc:'1')
yield()
}
}
; need infinite loop
}
sub task2() {
const ubyte x = 10
ubyte y
repeat {
for y in 5 to 18 {
txt.setchr(x, y-1, sc:' ')
txt.setchr(x, y, sc:'2')
yield()
}
for y in 18 downto 5 {
txt.setchr(x, y+1, sc:' ')
txt.setchr(x, y, sc:'2')
yield()
}
}
; need infinite loop
}
sub task3() {
ubyte x
const ubyte y = 10
repeat {
for x in 14 to 38 {
txt.setchr(x-1, y, sc:' ')
txt.setchr(x, y, sc:'3')
yield()
}
for x in 38 downto 14 {
txt.setchr(x+1, y, sc:' ')
txt.setchr(x, y, sc:'3')
yield()
}
}
; need infinite loop
}
sub task4() {
ubyte x
const ubyte y = 14
repeat {
for x in 15 to 30 {
txt.setchr(x-1, y, sc:' ')
txt.setchr(x, y, sc:'4')
yield()
}
for x in 30 downto 15 {
txt.setchr(x+1, y, sc:' ')
txt.setchr(x, y, sc:'4')
yield()
}
}
; need infinite loop
}
sub vsynctask() {
repeat {
sys.waitvsync()
sys.waitvsync()
yield()
}
; need infinite loop
}
}
+3 -3
View File
@@ -101,10 +101,10 @@ adpcm {
}
sub decode_nibble_second(ubyte @zp nibble) {
; Decoder for nibbles for the second channel.
; this is the hotspot of the decoder algorithm!
; Decoder for a single nibble for the second channel. (value of 'nibble' needs to be strictly 0-15 !)
; This is the hotspot of the decoder algorithm!
; Note that the generated assembly from this is pretty efficient,
; rewriting it by hand in asm seems to improve it only 5-10%
; rewriting it by hand in asm seems to improve it only ~10%.
cx16.r0s = 0 ; difference
if nibble & %0100 !=0
cx16.r0s += pstep_2
Binary file not shown.
Binary file not shown.
+172
View File
@@ -0,0 +1,172 @@
; Cooperative Multitasking example.
; Can be compiled for different targets (except virtual).
%import coroutines
%import textio
%zeropage basicsafe
main {
sub start() {
txt.print("cooperative multitasking / coroutines\n\n")
txt.print("here are couple of routines that each\nrun a few loops bouncing a digit around.\n")
txt.print("press any key to add a new counter task.")
coroutines.killall()
void coroutines.add(&task1, sc:'1')
void coroutines.add(&task2, sc:'2')
void coroutines.add(&task3, sc:'3')
void coroutines.add(&task4, sc:'4')
void coroutines.add(&keyhandler, 0)
void coroutines.add(&delaytask, 0)
coroutines.run(&supervisor) ; can also use 0 if you don't need a supervisor routine
; the supervisor is called every time a task switch is about to occur - and you can control that somewhat.
; if that control is not needed you could also just add a "system" routine as a regular task,
; much like the keyhandler and delay tasks above.
; we will end up here if there are no more tasks to run (doesn't happen in this example)
txt.print("we're all done!\n")
}
sub keyhandler() {
repeat {
ubyte key = cbm.GETIN2()
if key!=0 {
ubyte taskid = coroutines.add(&countertask, key)
counters[taskid] = 222
}
void coroutines.yield()
}
}
sub supervisor() -> bool {
; you can call coroutines.current() to get the id of the next task to run.
; (or just read the active_task variable)
; return true to execute that task, or false to skip it this time.
; or do something else....?
return true
}
ubyte[coroutines.MAX_TASKS] counters
sub countertask() {
repeat {
uword userdata = coroutines.yield() ; yield and obtain our userdata
ubyte tid = coroutines.current() ; what task are we?
counters[tid]-- ; our counter is in the array, it cannot be kept in a local variable (shared state)
txt.plot(15, 10 + tid)
if counters[tid] == 0 {
txt.print(" ")
return ; done, exit the task
} else {
txt.chrout(lsb(userdata))
txt.chrout(':')
txt.print_uw(counters[tid])
txt.spc()
}
}
}
sub task1() {
const ubyte x = 5
ubyte y
repeat 3 {
for y in 10 to 24 {
txt.setchr(x, y-1, sc:' ')
txt.setchr(x, y, sc:'1')
void coroutines.yield()
}
for y in 24 downto 10 {
txt.setchr(x, y+1, sc:' ')
txt.setchr(x, y, sc:'1')
void coroutines.yield()
}
}
txt.setchr(x, 10, sc:' ')
}
sub task2() {
const ubyte x = 10
ubyte y
repeat 2 {
for y in 9 to 22 {
txt.setchr(x, y-1, sc:' ')
txt.setchr(x, y, sc:'2')
void coroutines.yield()
}
for y in 22 downto 9 {
txt.setchr(x, y+1, sc:' ')
txt.setchr(x, y, sc:'2')
void coroutines.yield()
}
}
txt.setchr(x, 9, sc:' ')
; add a new task dynamically
void coroutines.add(&task5, 0)
}
sub task3() {
ubyte x
const ubyte y = 10
repeat 4 {
for x in 14 to 38 {
txt.setchr(x-1, y, sc:' ')
txt.setchr(x, y, sc:'3')
void coroutines.yield()
}
for x in 38 downto 14 {
txt.setchr(x+1, y, sc:' ')
txt.setchr(x, y, sc:'3')
void coroutines.yield()
}
}
txt.setchr(14, y, sc:' ')
}
sub task4() {
ubyte x
const ubyte y = 14
repeat 4 {
for x in 15 to 30 {
txt.setchr(x-1, y, sc:' ')
txt.setchr(x, y, sc:'4')
void coroutines.yield()
}
for x in 30 downto 15 {
txt.setchr(x+1, y, sc:' ')
txt.setchr(x, y, sc:'4')
void coroutines.yield()
}
}
txt.setchr(15, y, sc:' ')
}
sub task5() {
ubyte x
const ubyte y = 16
repeat 4 {
for x in 15 to 30 {
txt.setchr(x-1, y, sc:' ')
txt.setchr(x, y, sc:'5')
void coroutines.yield()
}
for x in 30 downto 15 {
txt.setchr(x+1, y, sc:' ')
txt.setchr(x, y, sc:'5')
void coroutines.yield()
}
}
txt.setchr(15, y, sc:' ')
}
sub delaytask() {
repeat 200 {
sys.waitvsync()
sys.waitvsync()
void coroutines.yield()
}
}
}
+13 -22
View File
@@ -1,31 +1,22 @@
%import textio
%import math
%zeropage basicsafe
%option no_sysinit
main {
sub start() {
str input = iso:"the quick brown fox jumps over the lazy dog"
bool bb
ubyte ub
uword uw
uw, void = thing2()
uw, bb = thing2()
uw, ub = thing2()
}
txt.print_uwhex(math.crc16(input, len(input)), true)
txt.nl()
math.crc32(input, len(input))
txt.print_uwhex(cx16.r15, true)
txt.print_uwhex(cx16.r14, false)
txt.nl()
math.crc32_start()
for cx16.r0L in input
math.crc32_update(cx16.r0L)
uword hiw,low
hiw,low = math.crc32_end_result()
txt.print_uwhex(hiw, true)
txt.print_uwhex(low, false)
txt.nl()
asmsub thing2() -> ubyte @A, bool @Pc {
%asm {{
lda #$aa
clc
rts
}}
}
}
Loaded 100 of 112 files, more files were not shown because too many files have changed in this diff. Show more