Compare commits

..

51 Commits

Author SHA1 Message Date
1f3c19169b first setup of LSP languageserver 2025-06-24 21:11:08 +02:00
1efdfe8ea1 much nicer colors in the bubbleuniverse example 2025-06-23 21:25:05 +02:00
67d4180825 lib 2025-06-20 22:01:29 +02:00
be31e190d2 shuffle arguments of 64tass command so the additional assembler options from custom targets actually work 2025-06-19 22:33:50 +02:00
b5d1575823 added boolean typed versions of the cx16.r0-r15 virtual registers 2025-06-18 00:05:10 +02:00
e3e395836d fix splitting of array decl and initializer for non numeric types 2025-06-13 23:31:56 +02:00
8dc2e47507 fix partial unused code removal in vm target 2025-06-11 23:31:29 +02:00
daf7c3357c better detection of missing return statement
preparing 11.4
2025-06-09 16:01:56 +02:00
e8795859c5 added sorting library for target virtual
added sorting routines that sort a values array together with the keys array
optimized gnomesort a little
2025-06-07 19:42:40 +02:00
bebe60b687 fix compiler crash on for x in wordvar, add sys.get_as_returnaddress() 2025-06-05 16:10:40 +02:00
ddceec364e optimized coroutines library 2025-06-04 21:34:32 +02:00
d067fa4b73 added strings.find_eol() 2025-06-03 21:09:44 +02:00
b5e51ab937 cleaner timings output 2025-06-02 19:30:25 +02:00
552e55c29f fix missing cmp #0 when asmsub call is part of a boolean expression 2025-06-02 19:22:00 +02:00
a228908c1a fix wrong address calculation for &wordarray[i] where i is a variable 2025-06-02 03:13:23 +02:00
15fc3b6c04 replace old antlr2kotlin code with the new visitor-based translator 2025-06-02 01:56:07 +02:00
0456badd02 creating on a new visitor-based antlr to kotlin translator 2025-06-02 01:18:07 +02:00
399cf5118d we will get a 11.4 version first before structs will land 2025-06-01 17:47:06 +02:00
a87f2640d3 fixed signed byte comparisons in case of overflowing values 2025-06-01 14:01:25 +02:00
a90ef274d7 fix word*128 codegen.
added cx16/landscape.p8 example that draws procedurally generated landscapes.
found bug in signed byte comparisons with overflow.
2025-05-31 05:27:19 +02:00
341778ba67 added -timings flag 2025-05-30 12:38:16 +02:00
ec50b5a007 homebrew info 2025-05-30 03:43:03 +02:00
31d84c8921 doc 2025-05-29 13:35:20 +02:00
34bedbeef1 optimize byte modulus (%) routine with repeated subtraction instead of using full division 2025-05-29 13:26:04 +02:00
3b1b0985c1 make sizeof(float) work, so you don't have to use sys.SIZEOF_FLOAT anymore etc.
define sys.SIZEOF_FLOAT in terms of sizeof(float)
2025-05-29 12:38:03 +02:00
368387e1a7 allow floats to be (explicitly) cast to integers 2025-05-26 21:39:48 +02:00
9da430ffeb vm: more complete V-flag handling. somd doc and todo updates. 2025-05-23 18:58:14 +02:00
cc063124cf add joystick control to cx16 fileselector.
fix fileselector Basic exasmple.
fixed too aggressive asm peephole optimization that destroyed %jumptable in libraries for example.
2025-05-23 17:50:11 +02:00
3b37b89951 added cx16.joysticks_detect() and cx16.joysticks_getall() 2025-05-23 02:26:21 +02:00
844b537d1e cobramk3 example now draws with new monogfx doublebuffering 2025-05-22 23:29:49 +02:00
caf1d4a22a fix monogfx INVERT draw mode 2025-05-22 21:29:23 +02:00
d8e244df99 fix monogfx example 2025-05-22 00:37:20 +02:00
548e421e27 added doublebuffering to monogfx (in both lores and hires mode) 2025-05-22 00:10:03 +02:00
322fa7ea69 slightly optimize monogfx plot() 2025-05-21 01:16:43 +02:00
cf7bea0985 cleanup RTS insertion and ast postprocessing before assembly generation 2025-05-21 00:19:50 +02:00
25d7f8808f IR: added signed multiplication opcodes 2025-05-20 21:36:05 +02:00
acc630972a make keyboardhandler example restartable 2025-05-15 23:07:54 +02:00
6a33be3fd8 IR: allow returning boolean in Pc/Pv cpu status register (Pz and Pn are not yet possible) 2025-05-15 22:56:45 +02:00
f5fc4e345c fix build error on case-insensitive filesystems 2025-05-15 21:11:34 +02:00
67231af623 fix forloop codegen over non-split word arrays of length >= 64 elements 2025-05-13 23:32:26 +02:00
e31ef6f06f IR: fix temp register type in for x in array 2025-05-13 22:23:04 +02:00
09d188106a different temp var mechanism for certain array expression, remove old tmpvar mechanism. 2025-05-13 21:12:10 +02:00
d8e2116481 different temp var mechanism for for loops, and pokef() 2025-05-13 21:06:33 +02:00
435dfbb932 optimize: rewrite suitable when into on..goto 2025-05-13 01:12:58 +02:00
ba93966474 optimize codegen: shortcut redundant jumps in when statement 2025-05-13 00:35:22 +02:00
ea8d17cdb2 optimized the cx16 multi-irq dispatcher used in cx16.enable_irq_handlers() 2025-05-12 23:26:54 +02:00
082265fb25 todo 2025-05-12 00:24:57 +02:00
9e557ce8ac add keyword 'on' to IDEA syntax 2025-05-11 23:32:54 +02:00
e5d9af75de remove double bra/jmp 2025-05-11 23:01:13 +02:00
31c1bf8bc5 added on..goto/call statement 2025-05-11 21:37:44 +02:00
37d4055036 translate newline '\n' to char code 13 in various encodings such as ISO (used to be 10)
This means that when printed, such newlines will now properly go to the next line in these encodings too (ISO variants, KATAKANA).
2025-05-11 19:45:24 +02:00
130 changed files with 4271 additions and 1865 deletions

View File

@ -4,8 +4,8 @@
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/0.24.0/org.eclipse.lsp4j-0.24.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/0.24.0/org.eclipse.lsp4j.jsonrpc-0.24.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.12.1/gson-2.12.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.36.0/error_prone_annotations-2.36.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.13.1/gson-2.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />

1
.idea/misc.xml generated
View File

@ -12,6 +12,7 @@
<option name="pkg" value="" />
<option name="language" value="Java" />
<option name="generateListener" value="false" />
<option name="generateVisitor" value="true" />
</PerGrammarGenerationSettings>
</list>
</option>

1
.idea/modules.xml generated
View File

@ -15,6 +15,7 @@
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
<module fileurl="file://$PROJECT_DIR$/languageServer/languageServer.iml" filepath="$PROJECT_DIR$/languageServer/languageServer.iml" />
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" />
<module fileurl="file://$PROJECT_DIR$/simpleAst/simpleAst.iml" filepath="$PROJECT_DIR$/simpleAst/simpleAst.iml" />

View File

@ -71,6 +71,7 @@ What does Prog8 provide?
- high-level program optimizations
- conditional branches that map 1:1 to cpu status flags
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
- ``on .. goto`` statement for fast jump tables
- ``in`` expression for concise and efficient multi-value/containment check
- ``defer`` statement to help write concise and robust subroutine cleanup logic
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``

View File

@ -27,7 +27,7 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
var golden: GoldenRam
val libraryPath: Path?
val customLauncher: List<String>
val additionalAssemblerOptions: String?
val additionalAssemblerOptions: List<String>
val defaultOutputType: OutputType
fun initializeMemoryAreas(compilerOptions: CompilationOptions)

View File

@ -6,12 +6,15 @@ import prog8.code.target.zp.C128Zeropage
import java.nio.file.Path
class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
class C128Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {

View File

@ -7,12 +7,15 @@ import java.io.IOException
import java.nio.file.Path
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class C64Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {

View File

@ -27,7 +27,7 @@ class ConfigFileTarget(
override val defaultOutputType: OutputType,
override val libraryPath: Path,
override val customLauncher: List<String>,
override val additionalAssemblerOptions: String?,
override val additionalAssemblerOptions: List<String>,
val ioAddresses: List<UIntRange>,
val zpScratchB1: UInt,
val zpScratchReg: UInt,
@ -37,7 +37,7 @@ class ConfigFileTarget(
val zpFullsafe: List<UIntRange>,
val zpKernalsafe: List<UIntRange>,
val zpBasicsafe: List<UIntRange>
): ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(8) {
): ICompilationTarget, IStringEncoding by Encoder(true), IMemSizer by NormalMemSizer(8) {
companion object {
@ -109,8 +109,6 @@ class ConfigFileTarget(
(customLauncherStr+"\n").lines().map { it.trimEnd() }
else emptyList()
val assemblerOptionsStr = props.getProperty("assembler_options", "").trim()
val assemblerOptions = assemblerOptionsStr.ifBlank { null }
val outputTypeString = props.getProperty("output_type", "PRG")
val defaultOutputType = OutputType.valueOf(outputTypeString.uppercase())
@ -128,7 +126,7 @@ class ConfigFileTarget(
defaultOutputType,
libraryPath,
customLauncher,
assemblerOptions,
if(assemblerOptionsStr=="") emptyList() else assemblerOptionsStr.split(" "),
ioAddresses,
props.getInteger("zp_scratch_b1"),
props.getInteger("zp_scratch_reg"),

View File

@ -6,12 +6,15 @@ import prog8.code.target.zp.CX16Zeropage
import java.nio.file.Path
class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class Cx16Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {

View File

@ -25,7 +25,7 @@ internal class NormalMemSizer(val floatsize: Int): IMemSizer {
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> floatsize * (numElements ?: 1)
dt.isLong -> throw IllegalArgumentException("long can not yet be put into memory")
dt.isLong -> 4 * (numElements ?: 1)
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
else -> 2 * (numElements ?: 1)
}

View File

@ -6,12 +6,15 @@ import prog8.code.target.zp.PETZeropage
import java.nio.file.Path
class PETTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
class PETTarget: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {

View File

@ -7,12 +7,15 @@ import kotlin.io.path.isReadable
import kotlin.io.path.name
import kotlin.io.path.readText
class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
class VMTarget: ICompilationTarget,
IStringEncoding by Encoder(false),
IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.ISO
override val libraryPath = null
override val customLauncher: List<String> = emptyList()
override val additionalAssemblerOptions = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
companion object {
@ -88,31 +91,6 @@ class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by Nor
zeropage = VirtualZeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
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, BaseDataType.STR -> numElements * 2
BaseDataType.FLOAT-> numElements * FLOAT_MEM_SIZE
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
}
}
else if (dt.isString) {
return numElements // treat it as the size of the given string with the length
?: 2 // treat it as the size to store a string pointer
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> 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)
}
}
}

View File

@ -197,6 +197,7 @@ object AtasciiEncoding {
fun encode(str: String): Result<List<UByte>, CharConversionException> {
val mapped = str.map { chr ->
when (chr) {
'\r' -> 0x9bu
'\u0000' -> 0u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly

View File

@ -285,6 +285,7 @@ object C64osEncoding {
val screencode = encodingC64os[chr]
return screencode?.toUByte() ?: when (chr) {
'\u0000' -> 0u
'\n' -> 13u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()

View File

@ -5,19 +5,19 @@ import prog8.code.core.Encoding
import prog8.code.core.IStringEncoding
import prog8.code.core.InternalCompilerException
object Encoder: IStringEncoding {
class Encoder(val newlineToCarriageReturn: Boolean): IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
val coded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true)
Encoding.SCREENCODES -> PetsciiEncoding.encodeScreencode(str, true)
Encoding.ISO -> IsoEncoding.encode(str)
Encoding.ATASCII -> AtasciiEncoding.encode(str)
Encoding.ISO5 -> IsoCyrillicEncoding.encode(str)
Encoding.ISO16 -> IsoEasternEncoding.encode(str)
Encoding.ISO -> IsoEncoding.encode(str, newlineToCarriageReturn)
Encoding.ISO5 -> IsoCyrillicEncoding.encode(str, newlineToCarriageReturn)
Encoding.ISO16 -> IsoEasternEncoding.encode(str, newlineToCarriageReturn)
Encoding.CP437 -> Cp437Encoding.encode(str)
Encoding.KATAKANA -> KatakanaEncoding.encode(str)
Encoding.KATAKANA -> KatakanaEncoding.encode(str, newlineToCarriageReturn)
Encoding.ATASCII -> AtasciiEncoding.encode(str)
Encoding.C64OS -> C64osEncoding.encode(str)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}
@ -30,12 +30,12 @@ object Encoder: IStringEncoding {
val decoded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true)
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)
Encoding.ISO -> IsoEncoding.decode(bytes)
Encoding.ATASCII -> AtasciiEncoding.decode(bytes)
Encoding.ISO5 -> IsoCyrillicEncoding.decode(bytes)
Encoding.ISO16 -> IsoEasternEncoding.decode(bytes)
Encoding.ISO -> IsoEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.ISO5 -> IsoCyrillicEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.ISO16 -> IsoEasternEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.CP437 -> Cp437Encoding.decode(bytes)
Encoding.KATAKANA -> KatakanaEncoding.decode(bytes)
Encoding.KATAKANA -> KatakanaEncoding.decode(bytes, newlineToCarriageReturn)
Encoding.ATASCII -> AtasciiEncoding.decode(bytes)
Encoding.C64OS -> C64osEncoding.decode(bytes)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}

View File

@ -6,14 +6,16 @@ import com.github.michaelbull.result.Result
import java.io.CharConversionException
import java.nio.charset.Charset
open class IsoEncodingBase(charsetName: String) {
val charset: Charset = Charset.forName(charsetName)
fun encode(str: String): Result<List<UByte>, CharConversionException> {
fun encode(str: String, newlineToCarriageReturn: Boolean): Result<List<UByte>, CharConversionException> {
return try {
val mapped = str.map { chr ->
when (chr) {
'\u0000' -> 0u
'\n' -> if(newlineToCarriageReturn) 13u else 10u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()
@ -27,9 +29,14 @@ open class IsoEncodingBase(charsetName: String) {
}
}
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
fun decode(bytes: Iterable<UByte>, newlineToCarriageReturn: Boolean): Result<String, CharConversionException> {
return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
Ok(String(bytes.map {
when(it) {
13u.toUByte() -> if(newlineToCarriageReturn) 10 else 13
else -> it.toByte()
}
}.toByteArray(), charset))
} catch (ce: CharConversionException) {
Err(ce)
}

View File

@ -64,10 +64,11 @@ object JapaneseCharacterConverter {
object KatakanaEncoding {
val charset: Charset = Charset.forName("JIS_X0201")
fun encode(str: String): Result<List<UByte>, CharConversionException> {
fun encode(str: String, newlineToCarriageReturn: Boolean): Result<List<UByte>, CharConversionException> {
return try {
val mapped = str.map { chr ->
when (chr) {
'\n' -> if(newlineToCarriageReturn) 13u else 10u
'\u0000' -> 0u
'\u00a0' -> 0xa0u // $a0 isn't technically a part of JIS X 0201 spec, and so we need to handle this ourselves
@ -112,9 +113,14 @@ object KatakanaEncoding {
}
}
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
fun decode(bytes: Iterable<UByte>, newlineToCarriageReturn: Boolean): Result<String, CharConversionException> {
return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
Ok(String(bytes.map {
when(it) {
13u.toUByte() -> if(newlineToCarriageReturn) 10 else 13
else -> it.toByte()
}
}.toByteArray(), charset))
} catch (ce: CharConversionException) {
Err(ce)
}

View File

@ -21,7 +21,7 @@ object PetsciiEncoding {
'\ufffe', // 0x07 -> UNDEFINED
'\uf118', // 0x08 -> DISABLE CHARACTER SET SWITCHING (CUS)
'\uf119', // 0x09 -> ENABLE CHARACTER SET SWITCHING (CUS)
'\ufffe', // 0x0A -> UNDEFINED
'\n', // 0x0A -> LINE FEED (RETURN)
'\ufffe', // 0x0B -> UNDEFINED
'\ufffe', // 0x0C -> UNDEFINED
'\n' , // 0x0D -> LINE FEED (RETURN)
@ -1117,6 +1117,8 @@ object PetsciiEncoding {
val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr]
return screencode?.toUByte() ?: when (chr) {
'\u0000' -> 0u
'\n' -> 141u
'\r' -> 141u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()

View File

@ -16,12 +16,6 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
init {
if (options.floats) {
throw InternalCompilerException("C128 target doesn't yet support floating point routines")
// note: in git commit labeled 'c128: remove floats module' the floats.p8 and floats.asm files are removed,
// they could be retrieved again at a later time if the compiler somehow *does* store the fp variables in bank1.
}
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,

View File

@ -19,7 +19,7 @@ class ConfigurableZeropage(
init {
if (options.floats) {
TODO("floats")
TODO("floats in configurable target zp")
}
if(SCRATCH_REG!=SCRATCH_B1+1u)
@ -30,7 +30,7 @@ class ConfigurableZeropage(
ZeropageType.FULL -> fullsafe.forEach { free.addAll(it) }
ZeropageType.BASICSAFE -> basicsafe.forEach { free.addAll(it) }
ZeropageType.KERNALSAFE -> kernalsafe.forEach { free.addAll(it) }
ZeropageType.FLOATSAFE -> TODO("floatsafe")
ZeropageType.FLOATSAFE -> TODO("floatsafe in configurable target zp")
}
val distinctFree = free.distinct()

View File

@ -16,10 +16,6 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_W2 = 0xb8u // temp storage 2 for a word
init {
if (options.floats) {
throw InternalCompilerException("PET target doesn't yet support floating point routines")
}
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,

View File

@ -432,23 +432,6 @@ class AsmGen6502Internal (
}
internal val tempVarsCounters = mutableMapOf(
BaseDataType.BOOL to 0,
BaseDataType.BYTE to 0,
BaseDataType.UBYTE to 0,
BaseDataType.WORD to 0,
BaseDataType.UWORD to 0,
BaseDataType.LONG to 0,
BaseDataType.FLOAT to 0
)
internal fun buildTempVarName(dt: BaseDataType, counter: Int): String = "prog8_tmpvar_${dt.toString().lowercase()}_$counter"
internal fun getTempVarName(dt: BaseDataType): String {
tempVarsCounters[dt] = tempVarsCounters.getValue(dt)+1
return buildTempVarName(dt, tempVarsCounters.getValue(dt))
}
internal fun loadByteFromPointerIntoA(pointervar: PtIdentifier): String {
// returns the source name of the zero page pointervar if it's already in the ZP,
// otherwise returns "P8ZP_SCRATCH_W1" which is the intermediary
@ -674,7 +657,8 @@ class AsmGen6502Internal (
}
}
expr.type.isFloat -> {
require(options.compTarget.FLOAT_MEM_SIZE == 5) {"invalid float size ${expr.position}"}
if(options.compTarget.FLOAT_MEM_SIZE != 5)
TODO("support float size other than 5 ${expr.position}")
assignExpressionToRegister(expr.index, RegisterOrPair.A)
out("""
sta P8ZP_SCRATCH_REG
@ -854,7 +838,7 @@ class AsmGen6502Internal (
private fun repeatWordCount(iterations: Int, stmt: PtRepeatLoop) {
require(iterations in 257..65536) { "invalid repeat count ${stmt.position}" }
val repeatLabel = makeLabel("repeat")
val counterVar = createRepeatCounterVar(BaseDataType.UWORD, isTargetCpu(CpuType.CPU65C02), stmt)
val counterVar = createTempVarReused(BaseDataType.UWORD, isTargetCpu(CpuType.CPU65C02), stmt)
val loopcount = if(iterations==65536) 0 else if(iterations and 0x00ff == 0) iterations else iterations + 0x0100 // so that the loop can simply use a double-dec
out("""
ldy #>$loopcount
@ -874,7 +858,7 @@ $repeatLabel""")
// note: A/Y must have been loaded with the number of iterations!
// the iny + double dec is microoptimization of the 16 bit loop
val repeatLabel = makeLabel("repeat")
val counterVar = createRepeatCounterVar(BaseDataType.UWORD, false, stmt)
val counterVar = createTempVarReused(BaseDataType.UWORD, false, stmt)
out("""
cmp #0
beq +
@ -897,13 +881,13 @@ $repeatLabel""")
require(count in 2..256) { "invalid repeat count ${stmt.position}" }
val repeatLabel = makeLabel("repeat")
if(isTargetCpu(CpuType.CPU65C02)) {
val counterVar = createRepeatCounterVar(BaseDataType.UBYTE, true, stmt)
val counterVar = createTempVarReused(BaseDataType.UBYTE, true, stmt)
out(" lda #${count and 255} | sta $counterVar")
out(repeatLabel)
translate(stmt.statements)
out(" dec $counterVar | bne $repeatLabel")
} else {
val counterVar = createRepeatCounterVar(BaseDataType.UBYTE, false, stmt)
val counterVar = createTempVarReused(BaseDataType.UBYTE, false, stmt)
out(" lda #${count and 255} | sta $counterVar")
out(repeatLabel)
translate(stmt.statements)
@ -915,13 +899,13 @@ $repeatLabel""")
val repeatLabel = makeLabel("repeat")
out(" cpy #0")
if(isTargetCpu(CpuType.CPU65C02)) {
val counterVar = createRepeatCounterVar(BaseDataType.UBYTE, true, stmt)
val counterVar = createTempVarReused(BaseDataType.UBYTE, true, stmt)
out(" beq $endLabel | sty $counterVar")
out(repeatLabel)
translate(stmt.statements)
out(" dec $counterVar | bne $repeatLabel")
} else {
val counterVar = createRepeatCounterVar(BaseDataType.UBYTE, false, stmt)
val counterVar = createTempVarReused(BaseDataType.UBYTE, false, stmt)
out(" beq $endLabel | sty $counterVar")
out(repeatLabel)
translate(stmt.statements)
@ -930,43 +914,9 @@ $repeatLabel""")
out(endLabel)
}
private fun createRepeatCounterVar(dt: BaseDataType, preferZeropage: Boolean, stmt: PtRepeatLoop): String {
val scope = stmt.definingISub()!!
val asmInfo = subroutineExtra(scope)
var parent = stmt.parent
while(parent !is PtProgram) {
if(parent is PtRepeatLoop)
break
parent = parent.parent
}
val isNested = parent is PtRepeatLoop
if(!isNested) {
// we can re-use a counter var from the subroutine if it already has one for that datatype
val existingVar = asmInfo.extraVars.firstOrNull { it.first==dt && it.second.endsWith("counter") }
if(existingVar!=null) {
if(!preferZeropage || existingVar.third!=null)
return existingVar.second
}
}
val counterVar = makeLabel("counter")
when(dt) {
BaseDataType.UBYTE, BaseDataType.UWORD -> {
val result = zeropage.allocate(counterVar, DataType.forDt(dt), null, stmt.position, errors)
result.fold(
success = { (address, _, _) -> asmInfo.extraVars.add(Triple(dt, counterVar, address)) },
failure = { asmInfo.extraVars.add(Triple(dt, counterVar, null)) } // allocate normally
)
return counterVar
}
else -> throw AssemblyError("invalidt dt")
}
}
private fun translate(stmt: PtWhen) {
val endLabel = makeLabel("when_end")
val choiceBlocks = mutableListOf<Pair<String, PtNodeGroup>>()
val choiceBlocks = mutableListOf<Pair<String, PtWhenChoice>>()
val conditionDt = stmt.value.type
if(conditionDt.isByte)
assignExpressionToRegister(stmt.value, RegisterOrPair.A)
@ -977,13 +927,20 @@ $repeatLabel""")
val choice = choiceNode as PtWhenChoice
var choiceLabel = makeLabel("choice")
if(choice.isElse) {
require(choice.parent.children.last() === choice)
translate(choice.statements)
// is always the last node so can fall through
} else {
if(choice.statements.children.isEmpty()) {
// no statements for this choice value, jump to the end immediately
choiceLabel = endLabel
} else {
choiceBlocks.add(choiceLabel to choice.statements)
val onlyJumpLabel = ((choice.statements.children.singleOrNull() as? PtJump)?.target as? PtIdentifier)?.name
if(onlyJumpLabel==null) {
choiceBlocks.add(choiceLabel to choice)
} else {
choiceLabel = onlyJumpLabel
}
}
for (cv in choice.values.children) {
val value = (cv as PtNumber).number.toInt()
@ -1000,11 +957,14 @@ $repeatLabel""")
}
}
}
jmp(endLabel)
if(choiceBlocks.isNotEmpty())
jmp(endLabel)
for(choiceBlock in choiceBlocks.withIndex()) {
out(choiceBlock.value.first)
translate(choiceBlock.value.second)
if (choiceBlock.index < choiceBlocks.size - 1)
translate(choiceBlock.value.second.statements)
if (choiceBlock.index < choiceBlocks.size - 1 && !choiceBlock.value.second.isOnlyGotoOrReturn())
jmp(endLabel)
}
out(endLabel)
@ -1085,18 +1045,24 @@ $repeatLabel""")
else {
if(evaluateAddressExpression) {
val arrayIdx = jump.target as? PtArrayIndexer
if (isTargetCpu(CpuType.CPU65C02) && arrayIdx!=null) {
if (!arrayIdx.splitWords) {
// if the jump target is an address in a non-split array (like a jump table of only pointers),
// on the 65c02, more optimal assembly can be generated using JMP (address,X)
assignExpressionToRegister(arrayIdx.index, RegisterOrPair.A)
out(" asl a | tax")
return JumpTarget(asmSymbolName(arrayIdx.variable), true, true, false)
if (arrayIdx!=null) {
if (isTargetCpu(CpuType.CPU65C02)) {
if (!arrayIdx.splitWords) {
// if the jump target is an address in a non-split array (like a jump table of only pointers),
// on the 65c02, more optimal assembly can be generated using JMP (address,X)
assignExpressionToRegister(arrayIdx.index, RegisterOrPair.A)
out(" asl a | tax")
return JumpTarget(asmSymbolName(arrayIdx.variable), true, true, false)
} else {
// print a message when more optimal code is possible for 65C02 cpu
val variable = symbolTable.lookup(arrayIdx.variable.name)!!
if(variable is StStaticVariable && variable.length!!<=128)
errors.info("the jump address array is @split, but @nosplit would create more efficient code here", jump.position)
}
} else {
// print a message when more optimal code is possible
val variable = symbolTable.lookup(arrayIdx.variable.name)!!
if(variable is StStaticVariable && variable.length!!<=128)
errors.info("the jump address array is @split, but @nosplit would create more efficient code here", jump.position)
// print a message when more optimal code is possible for 6502 cpu
if(!arrayIdx.splitWords)
errors.info("the jump address array is @nosplit, but @split would create more efficient code here", jump.position)
}
}
// we can do the address evaluation right now and just use a temporary pointer variable
@ -1557,6 +1523,51 @@ $repeatLabel""")
return "$GENERATED_LABEL_PREFIX${generatedLabelSequenceNumber}_$postfix"
}
internal fun createTempVarReused(dt: BaseDataType, preferZeropage: Boolean, stmt: PtNode): String {
val scope = stmt.definingISub()!!
val asmInfo = subroutineExtra(scope)
var parent = stmt.parent
while(parent !is PtProgram) {
if(parent is PtRepeatLoop || parent is PtForLoop)
break
parent = parent.parent
}
val isNested = parent is PtRepeatLoop || parent is PtForLoop
if(!isNested) {
// we can re-use a counter var from the subroutine if it already has one for that datatype
val existingVar = asmInfo.extraVars.firstOrNull { it.first==dt && it.second.endsWith("tempv") }
if(existingVar!=null) {
if(!preferZeropage || existingVar.third!=null) {
// println("reuse temp counter var: $dt ${existingVar.second} @${stmt.position}")
return existingVar.second
}
}
}
val counterVar = makeLabel("tempv")
// println("new temp counter var: $dt $counterVar @${stmt.position}")
when {
dt.isIntegerOrBool -> {
if(preferZeropage) {
val result = zeropage.allocate(counterVar, DataType.forDt(dt), null, stmt.position, errors)
result.fold(
success = { (address, _, _) -> asmInfo.extraVars.add(Triple(dt, counterVar, address)) },
failure = { asmInfo.extraVars.add(Triple(dt, counterVar, null)) } // allocate normally
)
} else {
asmInfo.extraVars.add(Triple(dt, counterVar, null)) // allocate normally
}
return counterVar
}
dt == BaseDataType.FLOAT -> {
asmInfo.extraVars.add(Triple(dt, counterVar, null)) // allocate normally, floats never on zeropage
return counterVar
}
else -> throw AssemblyError("invalid dt")
}
}
internal fun assignConstFloatToPointerAY(number: PtNumber) {
val floatConst = allocator.getFloatAsmConst(number.number)
out("""

View File

@ -512,6 +512,7 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
// rts + jmp -> remove jmp
// rts + bxx -> remove bxx
// lda + cmp #0 -> remove cmp, same for cpy and cpx.
// bra/jmp + bra/jmp -> remove second bra/jmp (bra bra / jmp jmp are not removed because this is likely a jump table!)
// and some other optimizations.
val mods = mutableListOf<Modification>()
@ -567,6 +568,15 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
}
}
}
// only remove bra followed by jmp or jmp followed by bra
// bra bra or jmp jmp is likely part of a jump table, which should keep all entries!
if((" bra" in first || "\tbra" in first) && (" jmp" in second || "\tjmp" in second)) {
mods.add(Modification(lines[1].index, true, null))
}
if((" jmp" in first || "\tjmp" in first) && (" bra" in second || "\tbra" in second)) {
mods.add(Modification(lines[1].index, true, null))
}
}
/*

View File

@ -28,6 +28,14 @@ internal class AssemblyProgram(
val assemblerCommand: List<String>
fun addRemainingOptions(command: MutableList<String>, program: Path, assembly: Path): List<String> {
if(options.compTarget.additionalAssemblerOptions.isNotEmpty())
command.addAll(options.compTarget.additionalAssemblerOptions)
command.addAll(listOf("--output", program.toString(), assembly.toString()))
return command
}
when(options.output) {
OutputType.PRG -> {
// CBM machines .prg generation.
@ -47,8 +55,7 @@ internal class AssemblyProgram(
command.add("--list=$listFile")
}
command.addAll(listOf("--output", prgFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command, prgFile, assemblyFile)
if(!options.quiet)
println("\nCreating prg for target ${compTarget.name}.")
}
@ -69,8 +76,7 @@ internal class AssemblyProgram(
if(options.asmListfile)
command.add("--list=$listFile")
command.addAll(listOf("--output", xexFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command,xexFile, assemblyFile)
if(!options.quiet)
println("\nCreating xex for target ${compTarget.name}.")
}
@ -90,8 +96,7 @@ internal class AssemblyProgram(
if(options.asmListfile)
command.add("--list=$listFile")
command.addAll(listOf("--output", binFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command, binFile, assemblyFile)
if(!options.quiet)
println("\nCreating raw binary for target ${compTarget.name}.")
}
@ -122,14 +127,10 @@ internal class AssemblyProgram(
command.add("--nostart") // should be headerless bin, because basic has problems doing a normal LOAD"lib",8,1 - need to use BLOAD
}
command.addAll(listOf("--output", binFile.toString(), assemblyFile.toString()))
assemblerCommand = command
assemblerCommand = addRemainingOptions(command, binFile, assemblyFile)
}
}
if(options.compTarget.additionalAssemblerOptions!=null)
assemblerCommand.add(options.compTarget.additionalAssemblerOptions!!)
val proc = ProcessBuilder(assemblerCommand)
if(!options.quiet)
proc.inheritIO()

View File

@ -739,7 +739,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.assignConstFloatToPointerAY(number)
}
else -> {
val tempvar = asmgen.getTempVarName(BaseDataType.FLOAT)
val tempvar = asmgen.createTempVarReused(BaseDataType.FLOAT, false, fcall)
asmgen.assignExpressionToVariable(fcall.args[1], tempvar, DataType.FLOAT)
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out("""

View File

@ -89,7 +89,7 @@ internal class ForLoopsAsmGen(
if(asmgen.options.romable) {
// cannot use self-modifying code, cannot use cpu stack (because loop can be interrupted halfway)
// so we need to store the loop end value in a newly allocated temporary variable
val toValueVar = asmgen.getTempVarName(iterableDt.elementType().base)
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A)
asmgen.out(" sta $toValueVar")
// pre-check for end already reached
@ -296,7 +296,7 @@ $modifiedLabel cmp #0 ; modified
if(asmgen.options.romable) {
// cannot use self-modifying code, cannot use cpu stack (because loop can be interrupted halfway)
// so we need to store the loop end value in a newly allocated temporary variable
val toValueVar = asmgen.getTempVarName(iterableDt.elementType().base)
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.out(" sta $toValueVar")
@ -420,6 +420,7 @@ $endLabel""")
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
asmgen.romableError("self-modifying code (forloop over words range)", stmt.position) // TODO fix romable; there is self-modifying code below
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
@ -442,7 +443,6 @@ $modifiedLabel sbc #0 ; modified
eor #$80
+ bpl $loopLabel
$endLabel""")
asmgen.romableError("self-modifying code (forloop over words range)", stmt.position) // TODO fix romable
}
private fun precheckFromToWord(iterableDt: DataType, stepsize: Int, fromVar: String, endLabel: String) {
@ -508,7 +508,7 @@ $endLabel""")
when {
iterableDt.isString -> {
if(asmgen.options.romable) {
val indexVar = asmgen.getTempVarName(BaseDataType.UBYTE)
val indexVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
asmgen.out("""
ldy #0
sty $indexVar
@ -539,7 +539,10 @@ $endLabel""")
}
}
iterableDt.isByteArray || iterableDt.isBoolArray -> {
val indexVar = if(asmgen.options.romable) asmgen.getTempVarName(iterableDt.elementType().base) else asmgen.makeLabel("for_index")
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(iterableDt.elementType().base, false, stmt)
else
asmgen.makeLabel("for_index")
asmgen.out("""
ldy #0
$loopLabel sty $indexVar
@ -576,7 +579,10 @@ $loopLabel sty $indexVar
asmgen.out(endLabel)
}
iterableDt.isSplitWordArray -> {
val indexVar = if(asmgen.options.romable) asmgen.getTempVarName(BaseDataType.UBYTE) else asmgen.makeLabel("for_index")
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
ldy #0
@ -616,8 +622,10 @@ $loopLabel sty $indexVar
asmgen.out(endLabel)
}
iterableDt.isWordArray -> {
val length = numElements * 2
val indexVar = if(asmgen.options.romable) asmgen.getTempVarName(BaseDataType.UBYTE) else asmgen.makeLabel("for_index")
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
ldy #0
@ -627,16 +635,16 @@ $loopLabel sty $indexVar
lda $iterableName+1,y
sta $loopvarName+1""")
asmgen.translate(stmt.statements)
if(length<=127) {
if(numElements<=127) {
asmgen.out("""
ldy $indexVar
iny
iny
cpy #$length
cpy #${numElements*2}
beq $endLabel
bne $loopLabel""")
} else {
// length is 128 words, 256 bytes
// array size is 128 words, 256 bytes
asmgen.out("""
ldy $indexVar
iny
@ -645,7 +653,7 @@ $loopLabel sty $indexVar
beq $endLabel""")
}
if(!asmgen.options.romable) {
if(length>=16) {
if(numElements>=16) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(

View File

@ -149,6 +149,93 @@ internal class IfElseAsmGen(private val program: PtProgram,
translateIfElseBodies("beq", ifElse)
}
private fun translateIfElseBodiesSignedByte(elseConditional: String, value: PtExpression, stmt: PtIfElse) {
fun branchElse(label: String) {
when (elseConditional) {
"<" -> {
asmgen.out("""
bvc +
eor #$80
+ bpl $label""")
}
">=" -> {
asmgen.out("""
bvc +
eor #$80
+ bmi $label""")
}
else -> throw AssemblyError("wrong conditional $elseConditional")
}
}
val afterIfLabel = asmgen.makeLabel("afterif")
asmgen.cmpAwithByteValue(value, true)
if(stmt.hasElse()) {
// if and else blocks
val elseLabel = asmgen.makeLabel("else")
branchElse(elseLabel)
asmgen.translate(stmt.ifScope)
asmgen.jmp(afterIfLabel)
asmgen.out(elseLabel)
asmgen.translate(stmt.elseScope)
} else {
// no else block
branchElse(afterIfLabel)
asmgen.translate(stmt.ifScope)
}
asmgen.out(afterIfLabel)
}
private fun translateJumpElseBodiesSignedByte(elseConditional: String, value: PtExpression, jump: PtJump, elseBlock: PtNodeGroup) {
fun branchTarget(label: String) {
when (elseConditional) {
"<" -> {
asmgen.out("""
bvc +
eor #$80
+ bmi $label""")
}
">=" -> {
asmgen.out("""
bvc +
eor #$80
+ bpl $label""")
}
else -> throw AssemblyError("wrong conditional $elseConditional")
}
}
fun branchElse(label: String) {
when (elseConditional) {
"<" -> {
asmgen.out("""
bvc +
eor #$80
+ bpl $label""")
}
">=" -> {
asmgen.out("""
bvc +
eor #$80
+ bmi $label""")
}
else -> throw AssemblyError("wrong conditional $elseConditional")
}
}
var target = asmgen.getJumpTarget(jump, false)
asmgen.cmpAwithByteValue(value, true)
if(target.indirect) {
branchElse("+")
if(target.needsExpressionEvaluation)
target = asmgen.getJumpTarget(jump)
asmgen.jmp(target.asmLabel, target.indirect, target.indexedX)
asmgen.out("+")
} else {
require(!target.needsExpressionEvaluation)
branchTarget(target.asmLabel)
}
asmgen.translate(elseBlock)
}
private fun translateIfElseBodies(elseBranchInstr: String, stmt: PtIfElse) {
// comparison value is already in A
val afterIfLabel = asmgen.makeLabel("afterif")
@ -212,38 +299,9 @@ internal class IfElseAsmGen(private val program: PtProgram,
translateIfElseBodies("beq", stmt)
}
"<" -> translateByteLess(stmt, signed, jumpAfterIf)
"<=" -> {
// X<=Y -> Y>=X (reverse of >=)
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, signed)
asmgen.cmpAwithByteValue(condition.left, false)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodies("bpl", "bmi", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bmi", stmt)
} else {
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
"<=" -> translateByteLessEqual(stmt, signed, jumpAfterIf)
">" -> translateByteGreater(stmt, signed, jumpAfterIf)
">=" -> {
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
asmgen.cmpAwithByteValue(condition.right, false)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodies("bpl", "bmi", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bmi", stmt)
} else {
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
">=" -> translateByteGreaterEqual(stmt, signed, jumpAfterIf)
in LogicalOperators -> {
val regAtarget = AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.BOOL, stmt.definingISub(), condition.position, register=RegisterOrPair.A)
if (assignmentAsmGen.optimizedLogicalExpr(condition, regAtarget)) {
@ -406,13 +464,13 @@ internal class IfElseAsmGen(private val program: PtProgram,
private fun translateByteLess(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
val condition = stmt.condition as PtBinaryExpression
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
asmgen.cmpAwithByteValue(condition.right, false)
if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodies("bmi", "bpl", jumpAfterIf, stmt.elseScope)
translateJumpElseBodiesSignedByte("<", condition.right, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bpl", stmt)
translateIfElseBodiesSignedByte("<", condition.right, stmt)
} else {
asmgen.cmpAwithByteValue(condition.right, false)
if(jumpAfterIf!=null)
translateJumpElseBodies("bcc", "bcs", jumpAfterIf, stmt.elseScope)
else
@ -420,16 +478,33 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
}
private fun translateByteLessEqual(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
// X<=Y -> Y>=X (reverse of >=)
val condition = stmt.condition as PtBinaryExpression
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, signed)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodiesSignedByte(">=", condition.left, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodiesSignedByte(">=", condition.left, stmt)
} else {
asmgen.cmpAwithByteValue(condition.left, false)
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
private fun translateByteGreater(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
val condition = stmt.condition as PtBinaryExpression
if(signed) {
// X>Y --> Y<X
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, true)
asmgen.cmpAwithByteValue(condition.left, true)
if (jumpAfterIf != null)
translateJumpElseBodies("bmi", "bpl", jumpAfterIf, stmt.elseScope)
translateJumpElseBodiesSignedByte("<", condition.left, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bpl", stmt)
translateIfElseBodiesSignedByte("<", condition.left, stmt)
} else {
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A)
asmgen.cmpAwithByteValue(condition.right, false)
@ -471,6 +546,23 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
}
private fun translateByteGreaterEqual(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) {
val condition = stmt.condition as PtBinaryExpression
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
return if(signed) {
if(jumpAfterIf!=null)
translateJumpElseBodiesSignedByte(">=", condition.right, jumpAfterIf, stmt.elseScope)
else
translateIfElseBodiesSignedByte(">=", condition.right, stmt)
} else {
asmgen.cmpAwithByteValue(condition.right, false)
if(jumpAfterIf!=null)
translateJumpElseBodies("bcs", "bcc", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bcc", stmt)
}
}
private fun translateIfWord(stmt: PtIfElse, condition: PtBinaryExpression, jumpAfterIf: PtJump?) {
val signed = condition.left.type.isSigned
val constValue = condition.right.asConstInteger()

View File

@ -45,7 +45,6 @@ internal class ProgramAndVarsGen(
}
memorySlabs()
tempVars()
footer()
}
}
@ -219,29 +218,6 @@ internal class ProgramAndVarsGen(
}
}
private fun tempVars() {
// these can be in the no clear section because they'll always get assigned a new value
asmgen.out("; expression temp vars\n .section BSS_NOCLEAR")
for((dt, count) in asmgen.tempVarsCounters) {
if(count>0) {
for(num in 1..count) {
val name = asmgen.buildTempVarName(dt, num)
when (dt) {
BaseDataType.BOOL -> asmgen.out("$name .byte ?")
BaseDataType.BYTE -> asmgen.out("$name .char ?")
BaseDataType.UBYTE -> asmgen.out("$name .byte ?")
BaseDataType.WORD -> asmgen.out("$name .sint ?")
BaseDataType.UWORD -> asmgen.out("$name .word ?")
BaseDataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.FLOAT_MEM_SIZE}")
BaseDataType.LONG -> throw AssemblyError("should not have a variable with long dt only constants")
else -> throw AssemblyError("weird dt for extravar $dt")
}
}
}
}
asmgen.out(" .send BSS_NOCLEAR")
}
private fun footer() {
var relocateBssVars = false
var relocateBssSlabs = false
@ -501,8 +477,8 @@ internal class ProgramAndVarsGen(
if(addr!=null)
asmgen.out("$name = $addr")
else when(dt) {
BaseDataType.UBYTE -> asmgen.out("$name .byte ?")
BaseDataType.UWORD -> asmgen.out("$name .word ?")
BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL -> asmgen.out("$name .byte ?")
BaseDataType.UWORD, BaseDataType.WORD -> asmgen.out("$name .word ?")
BaseDataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.FLOAT_MEM_SIZE}")
else -> throw AssemblyError("weird dt for extravar $dt")
}

View File

@ -973,7 +973,7 @@ internal class AssignmentAsmGen(
if(!directIntoY(expr.right)) asmgen.out(" pha")
assignExpressionToRegister(expr.right, RegisterOrPair.Y, false)
if(!directIntoY(expr.right)) asmgen.out(" pla")
asmgen.out(" jsr prog8_math.divmod_ub_asm")
asmgen.out(" jsr prog8_math.remainder_ub_asm")
if(target.register==RegisterOrPair.A)
asmgen.out(" cmp #0") // fix the status register
else
@ -1188,19 +1188,18 @@ internal class AssignmentAsmGen(
}
} else if(dt.isWord) {
if(shifts==7 && expr.operator == "<<") {
// optimized shift left 7 (*128) by first swapping the lsb/msb and then doing just one final shift
// optimized shift left 7 (*128) by swapping the lsb/msb and then doing just one final shift
assignExpressionToRegister(expr.left, RegisterOrPair.AY, signed)
asmgen.out("""
; shift left 7
sty P8ZP_SCRATCH_REG ; msb
sty P8ZP_SCRATCH_REG
lsr P8ZP_SCRATCH_REG
php ; save carry
ror a
sta P8ZP_SCRATCH_REG
lda #0
plp ; restore carry
ror P8ZP_SCRATCH_REG
ror a
ldy P8ZP_SCRATCH_REG""")
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
}
@ -1741,6 +1740,17 @@ internal class AssignmentAsmGen(
}
}
fun requiresCmp(expr: PtExpression) =
when (expr) {
is PtFunctionCall -> {
val function = asmgen.symbolTable.lookup(expr.name)
function is StExtSub // don't assume the extsub/asmsub has set the cpu flags correctly on exit, add an explicit cmp
}
is PtBuiltinFunctionCall -> true
is PtIfExpression -> true
else -> false
}
if(!expr.right.isSimple() && expr.operator!="xor") {
// shortcircuit evaluation into A
val shortcutLabel = asmgen.makeLabel("shortcut")
@ -1748,15 +1758,23 @@ internal class AssignmentAsmGen(
"and" -> {
// short-circuit LEFT and RIGHT --> if LEFT then RIGHT else LEFT (== if !LEFT then LEFT else RIGHT)
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
if(requiresCmp(expr.left))
asmgen.out(" cmp #0")
asmgen.out(" beq $shortcutLabel")
assignExpressionToRegister(expr.right, RegisterOrPair.A, false)
if(requiresCmp(expr.right))
asmgen.out(" cmp #0")
asmgen.out(shortcutLabel)
}
"or" -> {
// short-circuit LEFT or RIGHT --> if LEFT then LEFT else RIGHT
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
if(requiresCmp(expr.left))
asmgen.out(" cmp #0")
asmgen.out(" bne $shortcutLabel")
assignExpressionToRegister(expr.right, RegisterOrPair.A, false)
if(requiresCmp(expr.right))
asmgen.out(" cmp #0")
asmgen.out(shortcutLabel)
}
else -> throw AssemblyError("invalid logical operator")
@ -2609,10 +2627,10 @@ $endLabel""")
private fun assignAddressOf(target: AsmAssignTarget, sourceName: String, msb: Boolean, arrayDt: DataType?, arrayIndexExpr: PtExpression?) {
if(arrayIndexExpr!=null) {
val arrayName = if(arrayDt!!.isSplitWordArray) sourceName+"_lsb" else sourceName // the _lsb split array comes first in memory
val constIndex = arrayIndexExpr.asConstInteger()
if(constIndex!=null) {
if (arrayDt.isUnsignedWord) {
if (arrayDt!!.isUnsignedWord) {
// using a UWORD pointer with array indexing, always bytes
require(!msb)
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
if(constIndex in 1..255)
@ -2636,15 +2654,16 @@ $endLabel""")
else {
if(constIndex>0) {
val offset = if(arrayDt.isSplitWordArray) constIndex else program.memsizer.memorySize(arrayDt, constIndex) // add arrayIndexExpr * elementsize to the address of the array variable.
asmgen.out(" lda #<($arrayName + $offset) | ldy #>($arrayName + $offset)")
asmgen.out(" lda #<($sourceName + $offset) | ldy #>($sourceName + $offset)")
} else {
asmgen.out(" lda #<$arrayName | ldy #>$arrayName")
asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
}
}
assignRegisterpairWord(target, RegisterOrPair.AY)
return
} else {
if (arrayDt.isUnsignedWord) {
if (arrayDt!!.isUnsignedWord) {
// using a UWORD pointer with array indexing, always bytes
require(!msb)
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
asmgen.saveRegisterStack(CpuRegister.A, false)
@ -2679,10 +2698,29 @@ $endLabel""")
}
else {
assignExpressionToRegister(arrayIndexExpr, RegisterOrPair.A, false)
val subtype = arrayDt.sub!!
if(subtype.isByteOrBool) {
// elt size 1, we're good
} else if(subtype.isWord) {
if(!arrayDt.isSplitWordArray) {
// elt size 2
asmgen.out(" asl a")
}
} else if(subtype==BaseDataType.FLOAT) {
if(asmgen.options.compTarget.FLOAT_MEM_SIZE != 5)
TODO("support float size other than 5 ${arrayIndexExpr.position}")
asmgen.out("""
sta P8ZP_SCRATCH_REG
asl a
asl a
clc
adc P8ZP_SCRATCH_REG"""
)
} else throw AssemblyError("weird type $subtype")
asmgen.out("""
ldy #>$arrayName
ldy #>$sourceName
clc
adc #<$arrayName
adc #<$sourceName
bcc +
iny
+""")

View File

@ -205,7 +205,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" ldx P8ZP_SCRATCH_B1")
}
SourceStorageKind.EXPRESSION -> {
val tempVar = asmgen.getTempVarName(BaseDataType.UBYTE)
val tempVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, memory)
asmgen.out(" sta $tempVar")
if(value.expression is PtTypeCast)
inplacemodificationByteWithValue(tempVar, DataType.UBYTE, operator, value.expression)
@ -356,7 +356,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
SourceStorageKind.EXPRESSION -> {
val tempVar = asmgen.getTempVarName(BaseDataType.UBYTE)
val tempVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, target.array)
asmgen.out(" sta $tempVar")
if(value.expression is PtTypeCast)
inplacemodificationByteWithValue(tempVar, target.datatype, operator, value.expression)
@ -439,7 +439,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
SourceStorageKind.EXPRESSION -> {
val tempVar = asmgen.getTempVarName(BaseDataType.UWORD)
val tempVar = asmgen.createTempVarReused(BaseDataType.UWORD, false, target.array)
asmgen.out(" sta $tempVar | stx $tempVar+1")
if(value.expression is PtTypeCast)
inplacemodificationWordWithValue(tempVar, target.datatype, operator, value.expression, block)
@ -457,7 +457,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
target.datatype.isFloat -> {
// copy array value into tempvar
val tempvar = asmgen.getTempVarName(BaseDataType.FLOAT)
val tempvar = asmgen.createTempVarReused(BaseDataType.FLOAT, false, target.array)
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.A)
asmgen.out("""
ldy #>${target.asmVarname}
@ -910,7 +910,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"-" -> asmgen.out(" sec | sbc $otherName")
"*" -> asmgen.out(" ldy $otherName | jsr prog8_math.multiply_bytes")
"/" -> asmgen.out(" ldy $otherName | jsr prog8_math.divmod_ub_asm | tya")
"%" -> asmgen.out(" ldy $otherName | jsr prog8_math.divmod_ub_asm")
"%" -> asmgen.out(" ldy $otherName | jsr prog8_math.remainder_ub_asm")
"<<" -> {
asmgen.out("""
ldy $otherName
@ -1019,7 +1019,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
if(value==0)
throw AssemblyError("division by zero")
asmgen.out(" ldy #$value | jsr prog8_math.divmod_ub_asm")
asmgen.out(" ldy #$value | jsr prog8_math.remainder_ub_asm")
asmgen.storeAIntoZpPointerVar(sourceName, false)
}
"<<" -> {
@ -1190,7 +1190,7 @@ $shortcutLabel:""")
"%" -> {
if(signed)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" ldy $variable | jsr prog8_math.divmod_ub_asm")
asmgen.out(" ldy $variable | jsr prog8_math.remainder_ub_asm")
}
"<<" -> {
asmgen.out("""
@ -1363,7 +1363,7 @@ $shortcutLabel:""")
"%" -> {
if(signed)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" tay | lda $variable | jsr prog8_math.divmod_ub_asm")
asmgen.out(" tay | lda $variable | jsr prog8_math.remainder_ub_asm")
}
"<<" -> {
asmgen.out("""
@ -1534,7 +1534,7 @@ $shortcutLabel:""")
asmgen.out("""
lda $name
ldy #$value
jsr prog8_math.divmod_ub_asm
jsr prog8_math.remainder_ub_asm
sta $name""")
}
"<<" -> {
@ -1921,18 +1921,16 @@ $shortcutLabel:""")
asmgen.out(" lda #0 | sta $lsb")
}
value==7 -> {
// optimized shift left 7 (*128) by first swapping the lsb/msb and then doing just one final shift
// optimized shift left 7 (*128) by swapping the lsb/msb and then doing just one final shift
asmgen.out("""
; shift left 7
lsr $msb
php ; save carry
lda $lsb
ror a
sta $msb
lda #0
sta $lsb
plp ; restore carry
ror $msb
ror $lsb""")
ror a
sta $lsb""")
}
value>3 -> asmgen.out("""
ldy #$value

View File

@ -68,22 +68,22 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
private fun assignCpuRegister(returns: StExtSubParameter, regNum: Int, target: PtAssignTarget): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val loadCpuRegInstr = when(returns.register.registerOrPair) {
RegisterOrPair.A -> IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=regNum)
RegisterOrPair.X -> IRInstruction(Opcode.LOADHX, IRDataType.BYTE, reg1=regNum)
RegisterOrPair.Y -> IRInstruction(Opcode.LOADHY, IRDataType.BYTE, reg1=regNum)
RegisterOrPair.AX -> IRInstruction(Opcode.LOADHAX, IRDataType.WORD, reg1=regNum)
RegisterOrPair.AY -> IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=regNum)
RegisterOrPair.XY -> IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=regNum)
in Cx16VirtualRegisters -> IRInstruction(Opcode.LOADM, irType(returns.type), reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}")
RegisterOrPair.FAC1 -> IRInstruction(Opcode.LOADHFACZERO, IRDataType.FLOAT, fpReg1 = regNum)
RegisterOrPair.FAC2 -> IRInstruction(Opcode.LOADHFACONE, IRDataType.FLOAT, fpReg1 = regNum)
null -> {
TODO("assign CPU status flag ${returns.register.statusflag!!}")
}
else -> throw AssemblyError("cannot load register")
when(returns.register.registerOrPair) {
RegisterOrPair.A -> addInstr(result, IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=regNum), null)
RegisterOrPair.X -> addInstr(result, IRInstruction(Opcode.LOADHX, IRDataType.BYTE, reg1=regNum), null)
RegisterOrPair.Y -> addInstr(result, IRInstruction(Opcode.LOADHY, IRDataType.BYTE, reg1=regNum), null)
RegisterOrPair.AX -> addInstr(result, IRInstruction(Opcode.LOADHAX, IRDataType.WORD, reg1=regNum), null)
RegisterOrPair.AY -> addInstr(result, IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=regNum), null)
RegisterOrPair.XY -> addInstr(result, IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=regNum), null)
in Cx16VirtualRegisters -> addInstr(result, IRInstruction(Opcode.LOADM, irType(returns.type), reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}"), null)
RegisterOrPair.FAC1 -> addInstr(result, IRInstruction(Opcode.LOADHFACZERO, IRDataType.FLOAT, fpReg1 = regNum), null)
RegisterOrPair.FAC2 -> addInstr(result, IRInstruction(Opcode.LOADHFACONE, IRDataType.FLOAT, fpReg1 = regNum), null)
null -> if(returns.register.statusflag!=null)
result += assignCpuStatusFlagReturnvalue(returns.register.statusflag!!, regNum)
else
throw AssemblyError("weird CPU register")
else -> throw AssemblyError("weird CPU register")
}
addInstr(result, loadCpuRegInstr, null)
// build an assignment to store the value in the actual target.
val assign = PtAssignment(target.position)
@ -93,6 +93,30 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return result
}
private fun assignCpuStatusFlagReturnvalue(statusflag: Statusflag, regNum: Int): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
when(statusflag) {
Statusflag.Pc -> {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=regNum, immediate = 0)
it += IRInstruction(Opcode.ROXL, IRDataType.BYTE, reg1=regNum)
}
}
Statusflag.Pz -> TODO("find a way to assign cpu Z status bit to reg $regNum but it can already be clobbered by other return values")
Statusflag.Pn -> TODO("find a way to assign cpu Z status bit to reg $regNum but it can already be clobbered by other return values")
Statusflag.Pv -> {
val skipLabel = codeGen.createLabelName()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=regNum, immediate = 0)
it += IRInstruction(Opcode.BSTVC, labelSymbol = skipLabel)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=regNum, immediate = 1)
}
result += IRCodeChunk(skipLabel, null)
}
}
return result
}
internal fun translate(augAssign: PtAugmentedAssign): IRCodeChunks {
// augmented assignment always has just a single target
if (augAssign.target.children.single() is PtIrRegister)
@ -110,7 +134,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val chunks = when (augAssign.operator) {
"+=" -> operatorPlusInplace(symbol, array, constAddress, memTarget, targetDt, value)
"-=" -> operatorMinusInplace(symbol, array, constAddress, memTarget, targetDt, value)
"*=" -> operatorMultiplyInplace(symbol, array, constAddress, memTarget, targetDt, value)
"*=" -> operatorMultiplyInplace(symbol, array, constAddress, memTarget, targetDt, value, signed)
"/=" -> operatorDivideInplace(symbol, array, constAddress, memTarget, targetDt, value, signed)
"|=" -> operatorOrInplace(symbol, array, constAddress, memTarget, targetDt, value)
"or=" -> operatorLogicalOrInplace(symbol, array, constAddress, memTarget, targetDt, value)
@ -207,7 +231,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val tr = expressionEval.translateExpression(array.index)
addToResult(result, tr, tr.resultReg, -1)
if(!array.splitWords && eltSize>1)
result += codeGen.multiplyByConst(IRDataType.BYTE, tr.resultReg, eltSize)
result += codeGen.multiplyByConst(DataType.UBYTE, tr.resultReg, eltSize)
return tr.resultReg
}
@ -769,7 +793,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return result
}
private fun operatorMultiplyInplace(symbol: String?, array: PtArrayIndexer?, constAddress: Int?, memory: PtMemoryByte?, vmDt: IRDataType, operand: PtExpression): IRCodeChunks? {
private fun operatorMultiplyInplace(symbol: String?, array: PtArrayIndexer?, constAddress: Int?, memory: PtMemoryByte?, vmDt: IRDataType, operand: PtExpression, signed: Boolean): IRCodeChunks? {
if(array!=null) {
val eltSize = codeGen.program.memsizer.memorySize(array.type, null)
val result = mutableListOf<IRCodeChunkBase>()
@ -783,7 +807,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val valueReg=codeGen.registers.next(eltDt)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, eltDt, reg1=valueReg, immediate = constValue)
it += IRInstruction(Opcode.MULM, eltDt, reg1=valueReg, labelSymbol = array.variable.name, symbolOffset = constIndex*eltSize)
val opcode = if(signed) Opcode.MULSM else Opcode.MULM
it += IRInstruction(opcode, eltDt, reg1=valueReg, labelSymbol = array.variable.name, symbolOffset = constIndex*eltSize)
}
}
return result
@ -803,22 +828,23 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val tr = expressionEval.translateExpression(operand)
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, if(constAddress!=null)
IRInstruction(Opcode.MULM, vmDt, fpReg1 = tr.resultFpReg, address = constAddress)
IRInstruction(Opcode.MULSM, vmDt, fpReg1 = tr.resultFpReg, address = constAddress)
else
IRInstruction(Opcode.MULM, vmDt, fpReg1 = tr.resultFpReg, labelSymbol = symbol)
IRInstruction(Opcode.MULSM, vmDt, fpReg1 = tr.resultFpReg, labelSymbol = symbol)
, null)
}
} else {
if(constFactorRight!=null && !constFactorRight.type.isFloat) {
val factor = constFactorRight.number.toInt()
result += codeGen.multiplyByConstInplace(vmDt, constAddress, symbol, factor)
result += codeGen.multiplyByConstInplace(vmDt, signed, constAddress, symbol, factor)
} else {
val tr = expressionEval.translateExpression(operand)
addToResult(result, tr, tr.resultReg, -1)
val opcode = if(signed) Opcode.MULSM else Opcode.MULM
addInstr(result, if(constAddress!=null)
IRInstruction(Opcode.MULM, vmDt, reg1=tr.resultReg, address = constAddress)
IRInstruction(opcode, vmDt, reg1=tr.resultReg, address = constAddress)
else
IRInstruction(Opcode.MULM, vmDt, reg1=tr.resultReg, labelSymbol = symbol)
IRInstruction(opcode, vmDt, reg1=tr.resultReg, labelSymbol = symbol)
, null)
}
}

View File

@ -3,6 +3,7 @@ package prog8.codegen.intermediate
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.BaseDataType
import prog8.code.core.DataType
import prog8.intermediate.*
@ -662,7 +663,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = target.variable.name)
@ -683,7 +684,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(IRDataType.BYTE, indexTr.resultReg, eltSize)
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
if(msb)
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexTr.resultReg)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = target.variable.name)

View File

@ -2,10 +2,7 @@ package prog8.codegen.intermediate
import prog8.code.*
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.BaseDataType
import prog8.code.core.Cx16VirtualRegisters
import prog8.code.core.Statusflag
import prog8.code.core.*
import prog8.intermediate.*
@ -392,7 +389,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val tr = translateExpression(arrayIx.index)
addToResult(result, tr, tr.resultReg, -1)
if(eltSize>1)
result += codeGen.multiplyByConst(IRDataType.BYTE, tr.resultReg, eltSize)
result += codeGen.multiplyByConst(DataType.UBYTE, tr.resultReg, eltSize)
if(vmDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1 = resultFpRegister, reg1=tr.resultReg, labelSymbol = arrayVarSymbol), null)
@ -565,8 +562,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return when(binExpr.operator) {
"+" -> operatorPlus(binExpr, vmDt)
"-" -> operatorMinus(binExpr, vmDt)
"*" -> operatorMultiply(binExpr, vmDt)
"/" -> operatorDivide(binExpr, vmDt, signed)
"*" -> operatorMultiply(binExpr, binExpr.left.type)
"/" -> operatorDivide(binExpr, binExpr.left.type)
"%" -> operatorModulo(binExpr, vmDt)
"|" -> operatorOr(binExpr, vmDt, true)
"&" -> operatorAnd(binExpr, vmDt, true)
@ -1049,7 +1046,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
private fun loadStatusAsBooleanResult(branchForTrue: Opcode, result: MutableList<IRCodeChunkBase>): Int {
// TODO this used to be a single instruction like SCC, SCS, SZ etc but those were problematic
// TODO this used to be a single instruction like SCC, SCS, SZ etc
val other = codeGen.createLabelName()
val after = codeGen.createLabelName()
val resultReg = codeGen.registers.next(IRDataType.BYTE)
@ -1064,7 +1061,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
private fun compareRegisterAsBooleanResult(branchForTrue: Opcode, dt: IRDataType, reg1: Int, reg2: Int, result: MutableList<IRCodeChunkBase>): Int {
// TODO this used to be a single instruction like SCC, SCS, SZ etc but those were problematic
// TODO this used to be a single instruction like SCC, SCS, SZ etc
val other = codeGen.createLabelName()
val after = codeGen.createLabelName()
val resultReg = codeGen.registers.next(IRDataType.BYTE)
@ -1224,7 +1221,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun operatorDivide(binExpr: PtBinaryExpression, vmDt: IRDataType, signed: Boolean): ExpressionCodeResult {
private fun operatorDivide(binExpr: PtBinaryExpression, dt: DataType): ExpressionCodeResult {
val vmDt = irType(dt)
val result = mutableListOf<IRCodeChunkBase>()
val constFactorRight = binExpr.right as? PtNumber
if(vmDt==IRDataType.FLOAT) {
@ -1239,7 +1237,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addToResult(result, leftTr, -1, leftTr.resultFpReg)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
addInstr(result, if(signed)
addInstr(result, if(dt.isSigned)
IRInstruction(Opcode.DIVSR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2=rightTr.resultFpReg)
else
IRInstruction(Opcode.DIVR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2=rightTr.resultFpReg)
@ -1251,13 +1249,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
val factor = constFactorRight.number.toInt()
result += codeGen.divideByConst(vmDt, tr.resultReg, factor, signed)
result += codeGen.divideByConst(vmDt, tr.resultReg, factor, dt.isSigned)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
if(binExpr.right is PtNumber) {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
addInstr(result, if (signed)
addInstr(result, if (dt.isSigned)
IRInstruction(Opcode.DIVS, vmDt, reg1 = leftTr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt())
else
IRInstruction(Opcode.DIV, vmDt, reg1 = leftTr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt())
@ -1268,7 +1266,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addToResult(result, leftTr, leftTr.resultReg, -1)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, if (signed)
addInstr(result, if (dt.isSigned)
IRInstruction(Opcode.DIVSR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg)
else
IRInstruction(Opcode.DIVR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg)
@ -1279,7 +1277,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun operatorMultiply(binExpr: PtBinaryExpression, vmDt: IRDataType): ExpressionCodeResult {
private fun operatorMultiply(binExpr: PtBinaryExpression, dt: DataType): ExpressionCodeResult {
val vmDt = irType(dt)
val result = mutableListOf<IRCodeChunkBase>()
val constFactorLeft = binExpr.left as? PtNumber
val constFactorRight = binExpr.right as? PtNumber
@ -1301,7 +1300,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addToResult(result, leftTr, -1, leftTr.resultFpReg)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
addInstr(result, IRInstruction(Opcode.MULR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
addInstr(result, IRInstruction(Opcode.MULSR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg)
}
} else {
@ -1309,20 +1308,21 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val tr = translateExpression(binExpr.right)
addToResult(result, tr, tr.resultReg, -1)
val factor = constFactorLeft.number.toInt()
result += codeGen.multiplyByConst(vmDt, tr.resultReg, factor)
result += codeGen.multiplyByConst(dt, tr.resultReg, factor)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else if(constFactorRight!=null && !constFactorRight.type.isFloat) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
val factor = constFactorRight.number.toInt()
result += codeGen.multiplyByConst(vmDt, tr.resultReg, factor)
result += codeGen.multiplyByConst(dt, tr.resultReg, factor)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.MULR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
val opcode = if(dt.isSigned) Opcode.MULSR else Opcode.MULR
addInstr(result, IRInstruction(opcode, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
}
}

View File

@ -256,8 +256,8 @@ class IRCodeGen(
is PtBool,
is PtArray,
is PtBlock,
is PtDefer -> throw AssemblyError("should have been transformed")
is PtString -> throw AssemblyError("should not occur as separate statement node ${node.position}")
is PtDefer -> throw AssemblyError("defer should have been transformed")
is PtString -> throw AssemblyError("string should not occur as separate statement node ${node.position}")
is PtSub -> throw AssemblyError("nested subroutines should have been flattened ${node.position}")
else -> TODO("missing codegen for $node")
}
@ -420,8 +420,9 @@ class IRCodeGen(
whenStmt.choices.children.forEach {
val choice = it as PtWhenChoice
if(choice.isElse) {
require(choice.parent.children.last() === choice)
result += translateNode(choice.statements)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
// is always the last node so can fall through
} else {
if(choice.statements.children.isEmpty()) {
// no statements for this choice value, jump to the end immediately
@ -433,22 +434,30 @@ class IRCodeGen(
}
} else {
val choiceLabel = createLabelName()
choices.add(choiceLabel to choice)
val onlyJumpLabel = ((choice.statements.children.singleOrNull() as? PtJump)?.target as? PtIdentifier)?.name
val branchLabel: String
if(onlyJumpLabel==null) {
choices.add(choiceLabel to choice)
branchLabel = choiceLabel
} else {
branchLabel = onlyJumpLabel
}
choice.values.children.map { v -> v as PtNumber }.sortedBy { v -> v.number }.forEach { value ->
result += IRCodeChunk(null, null).also { chunk ->
chunk += IRInstruction(Opcode.CMPI, valueDt, reg1=valueTr.resultReg, immediate = value.number.toInt())
chunk += IRInstruction(Opcode.BSTEQ, labelSymbol = choiceLabel)
chunk += IRInstruction(Opcode.BSTEQ, labelSymbol = branchLabel)
}
}
}
}
}
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
if(choices.isNotEmpty())
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
choices.forEach { (label, choice) ->
result += labelFirstChunk(translateNode(choice.statements), label)
val lastStatement = choice.statements.children.last()
if(lastStatement !is PtReturn && lastStatement !is PtJump)
if(!choice.isOnlyGotoOrReturn())
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
}
@ -469,10 +478,11 @@ class IRCodeGen(
}
is PtIdentifier -> {
require(forLoop.variable.name == loopvar.scopedName)
val elementDt = irType(iterable.type.elementType())
val iterableLength = symbolTable.getLength(iterable.name)
val loopvarSymbol = forLoop.variable.name
val indexReg = registers.next(IRDataType.BYTE)
val tmpReg = registers.next(IRDataType.BYTE)
val tmpReg = registers.next(elementDt)
val loopLabel = createLabelName()
val endLabel = createLabelName()
when {
@ -480,9 +490,9 @@ class IRCodeGen(
// iterate over a zero-terminated string
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
it += IRInstruction(Opcode.LOADX, elementDt, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tmpReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.STOREM, elementDt, reg1 = tmpReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
val jumpChunk = IRCodeChunk(null, null)
@ -493,8 +503,7 @@ class IRCodeGen(
}
iterable.type.isSplitWordArray -> {
// iterate over lsb/msb split word array
val elementDt = iterable.type.elementType()
if(!elementDt.isWord)
if(elementDt!=IRDataType.WORD)
throw AssemblyError("weird dt")
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
@ -504,7 +513,7 @@ class IRCodeGen(
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2=indexReg, labelSymbol=iterable.name+"_msb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegLsb, reg2=indexReg, labelSymbol=iterable.name+"_lsb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=concatReg, reg2=tmpRegMsb, reg3=tmpRegLsb)
it += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=concatReg, labelSymbol = loopvarSymbol)
it += IRInstruction(Opcode.STOREM, elementDt, reg1=concatReg, labelSymbol = loopvarSymbol)
}
result += translateNode(forLoop.statements)
result += IRCodeChunk(null, null).also {
@ -771,7 +780,7 @@ class IRCodeGen(
code += if(factor==0.0) {
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = 0.0)
} else {
IRInstruction(Opcode.MUL, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
IRInstruction(Opcode.MULS, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
}
return code
}
@ -789,38 +798,40 @@ class IRCodeGen(
val factorReg = registers.next(IRDataType.FLOAT)
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1=factorReg, immediateFp = factor)
code += if(knownAddress!=null)
IRInstruction(Opcode.MULM, IRDataType.FLOAT, fpReg1 = factorReg, address = knownAddress)
IRInstruction(Opcode.MULSM, IRDataType.FLOAT, fpReg1 = factorReg, address = knownAddress)
else
IRInstruction(Opcode.MULM, IRDataType.FLOAT, fpReg1 = factorReg, labelSymbol = symbol)
IRInstruction(Opcode.MULSM, IRDataType.FLOAT, fpReg1 = factorReg, labelSymbol = symbol)
}
return code
}
internal fun multiplyByConst(dt: IRDataType, reg: Int, factor: Int): IRCodeChunk {
internal fun multiplyByConst(dt: DataType, reg: Int, factor: Int): IRCodeChunk {
val irdt = irType(dt)
val code = IRCodeChunk(null, null)
if(factor==1)
return code
val pow2 = powersOfTwoInt.indexOf(factor)
if(pow2==1) {
// just shift 1 bit
code += IRInstruction(Opcode.LSL, dt, reg1=reg)
code += IRInstruction(Opcode.LSL, irdt, reg1=reg)
}
else if(pow2>=1) {
// just shift multiple bits
val pow2reg = registers.next(IRDataType.BYTE)
code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=pow2reg, immediate = pow2)
code += IRInstruction(Opcode.LSLN, dt, reg1=reg, reg2=pow2reg)
code += IRInstruction(Opcode.LSLN, irdt, reg1=reg, reg2=pow2reg)
} else {
code += if (factor == 0) {
IRInstruction(Opcode.LOAD, dt, reg1=reg, immediate = 0)
IRInstruction(Opcode.LOAD, irdt, reg1=reg, immediate = 0)
} else {
IRInstruction(Opcode.MUL, dt, reg1=reg, immediate = factor)
val opcode = if(dt.isSigned) Opcode.MULS else Opcode.MUL
IRInstruction(opcode, irdt, reg1=reg, immediate = factor)
}
}
return code
}
internal fun multiplyByConstInplace(dt: IRDataType, knownAddress: Int?, symbol: String?, factor: Int): IRCodeChunk {
internal fun multiplyByConstInplace(dt: IRDataType, signed: Boolean, knownAddress: Int?, symbol: String?, factor: Int): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1)
return code
@ -850,10 +861,11 @@ class IRCodeGen(
else {
val factorReg = registers.next(dt)
code += IRInstruction(Opcode.LOAD, dt, reg1=factorReg, immediate = factor)
val opcode = if(signed) Opcode.MULSM else Opcode.MULM
code += if(knownAddress!=null)
IRInstruction(Opcode.MULM, dt, reg1=factorReg, address = knownAddress)
IRInstruction(opcode, dt, reg1=factorReg, address = knownAddress)
else
IRInstruction(Opcode.MULM, dt, reg1=factorReg, labelSymbol = symbol)
IRInstruction(opcode, dt, reg1=factorReg, labelSymbol = symbol)
}
}
return code
@ -1954,7 +1966,7 @@ class IRCodeGen(
null -> when(registerOrFlag.statusflag) {
// TODO: do the statusflag argument as last
Statusflag.Pc -> chunk += IRInstruction(Opcode.LSR, paramDt, reg1=resultReg)
else -> throw AssemblyError("weird statusflag as param")
else -> throw AssemblyError("unsupported statusflag as param")
}
else -> throw AssemblyError("unsupported register arg")
}

View File

@ -357,7 +357,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
when (ins.opcode) {
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MULS, Opcode.MOD -> {
if (ins.immediate == 1) {
chunk.instructions.removeAt(idx)
changed = true

View File

@ -48,6 +48,36 @@ class IRUnusedCodeRemover(
}
irprog.st.removeTree(blockLabel)
removeBlockInits(irprog, blockLabel)
}
private fun removeBlockInits(code: IRProgram, blockLabel: String) {
val instructions = code.globalInits.instructions
instructions.toTypedArray().forEach {ins ->
if(ins.labelSymbol?.startsWith(blockLabel)==true) {
instructions.remove(ins)
}
}
// remove stray loads
instructions.toTypedArray().forEach { ins ->
if(ins.opcode in arrayOf(Opcode.LOAD, Opcode.LOADR, Opcode.LOADM)) {
if(ins.reg1!=0) {
if(instructions.count { it.reg1==ins.reg1 || it.reg2==ins.reg1 } <2) {
if(ins.labelSymbol!=null)
code.st.removeIfExists(ins.labelSymbol!!)
instructions.remove(ins)
}
}
else if(ins.fpReg1!=0) {
if (instructions.count { it.fpReg1 == ins.fpReg1 || it.fpReg2 == ins.fpReg1 } < 2) {
if(ins.labelSymbol!=null)
code.st.removeIfExists(ins.labelSymbol!!)
instructions.remove(ins)
}
}
}
}
}
private fun removeUnusedSubroutines(): Int {

View File

@ -49,7 +49,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
if(truepart.statements.singleOrNull() is Jump) {
return listOf(
IAstModification.InsertAfter(ifElse, elsepart, parent as IStatementContainer),
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse)
IAstModification.ReplaceNode(elsepart, AnonymousScope.empty(), ifElse)
)
}
if(elsepart.statements.singleOrNull() is Jump) {
@ -57,7 +57,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
return listOf(
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
IAstModification.InsertAfter(ifElse, truepart, parent as IStatementContainer),
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse),
IAstModification.ReplaceNode(elsepart, AnonymousScope.empty(), ifElse),
IAstModification.ReplaceNode(truepart, elsepart, ifElse)
)
}

View File

@ -82,12 +82,11 @@ class StatementOptimizer(private val program: Program,
// empty true part? switch with the else part
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isNotEmpty()) {
val invertedCondition = invertCondition(ifElse.condition, program)
val emptyscope = AnonymousScope(mutableListOf(), ifElse.elsepart.position)
val truepart = AnonymousScope(ifElse.elsepart.statements, ifElse.truepart.position)
return listOf(
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
IAstModification.ReplaceNode(ifElse.truepart, truepart, ifElse),
IAstModification.ReplaceNode(ifElse.elsepart, emptyscope, ifElse)
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope.empty(), ifElse)
)
}
@ -106,7 +105,7 @@ class StatementOptimizer(private val program: Program,
if(ifElse.truepart.statements.singleOrNull() is Return) {
val elsePart = AnonymousScope(ifElse.elsepart.statements, ifElse.elsepart.position)
return listOf(
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope(mutableListOf(), ifElse.elsepart.position), ifElse),
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope.empty(), ifElse),
IAstModification.InsertAfter(ifElse, elsePart, parent as IStatementContainer)
)
}
@ -146,7 +145,7 @@ class StatementOptimizer(private val program: Program,
if (range.size() == 1) {
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val scope = AnonymousScope(mutableListOf(), forLoop.position)
val scope = AnonymousScope.empty(forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
@ -161,7 +160,7 @@ class StatementOptimizer(private val program: Program,
// loop over string of length 1 -> just assign the single character
val character = options.compTarget.encodeString(sv.value, sv.encoding)[0]
val byte = NumericLiteral(BaseDataType.UBYTE, character.toDouble(), iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position)
val scope = AnonymousScope.empty(forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
@ -173,7 +172,7 @@ class StatementOptimizer(private val program: Program,
// loop over array of length 1 -> just assign the single value
val av = (iterable.value as ArrayLiteral).value[0].constValue(program)?.number
if(av!=null) {
val scope = AnonymousScope(mutableListOf(), forLoop.position)
val scope = AnonymousScope.empty(forLoop.position)
scope.statements.add(Assignment(
AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), NumericLiteral.optimalInteger(av.toInt(), iterable.position),
AssignmentOrigin.OPTIMIZER, forLoop.position))
@ -466,7 +465,7 @@ class StatementOptimizer(private val program: Program,
return IfElse(
compare,
AnonymousScope(mutableListOf(assign), position),
AnonymousScope(mutableListOf(), position),
AnonymousScope.empty(),
position
)
}
@ -501,6 +500,27 @@ class StatementOptimizer(private val program: Program,
}
}
if(whenStmt.betterAsOnGoto(program, options)) {
// rewrite when into a on..goto , which is faster and also smaller for ~5+ cases
var elseJump: Jump? = null
val jumps = mutableListOf<Pair<Int, Jump>>()
whenStmt.choices.forEach { choice ->
if(choice.values==null) {
elseJump = choice.statements.statements.single() as Jump
} else {
choice.values!!.forEach { value ->
jumps.add(value.constValue(program)!!.number.toInt() to choice.statements.statements.single() as Jump)
}
}
}
val jumpLabels = jumps.sortedBy { it.first }.map { it.second.target as IdentifierReference }
val elsePart = if(elseJump==null) null else AnonymousScope(mutableListOf(elseJump), elseJump.position)
val onGoto = OnGoto(false, whenStmt.condition, jumpLabels, elsePart, whenStmt.position)
return listOf(IAstModification.ReplaceNode(whenStmt, onGoto, parent))
}
return noModifications
}

View File

@ -419,12 +419,13 @@ sys {
const ubyte target = 128 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 5
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@ -946,6 +947,18 @@ _no_msb_size
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
@ -984,6 +997,7 @@ cx16 {
&uword r14 = $1bfc
&uword r15 = $1bfe
; signed word versions
&word r0s = $1be0
&word r1s = $1be2
&word r2s = $1be4
@ -1001,6 +1015,7 @@ cx16 {
&word r14s = $1bfc
&word r15s = $1bfe
; ubyte versions (low and high bytes)
&ubyte r0L = $1be0
&ubyte r1L = $1be2
&ubyte r2L = $1be4
@ -1035,6 +1050,7 @@ cx16 {
&ubyte r14H = $1bfd
&ubyte r15H = $1bff
; signed byte versions (low and high bytes)
&byte r0sL = $1be0
&byte r1sL = $1be2
&byte r2sL = $1be4
@ -1069,6 +1085,42 @@ cx16 {
&byte r14sH = $1bfd
&byte r15sH = $1bff
; boolean versions (low and high bytes)
&bool r0bL = $1be0
&bool r1bL = $1be2
&bool r2bL = $1be4
&bool r3bL = $1be6
&bool r4bL = $1be8
&bool r5bL = $1bea
&bool r6bL = $1bec
&bool r7bL = $1bee
&bool r8bL = $1bf0
&bool r9bL = $1bf2
&bool r10bL = $1bf4
&bool r11bL = $1bf6
&bool r12bL = $1bf8
&bool r13bL = $1bfa
&bool r14bL = $1bfc
&bool r15bL = $1bfe
&bool r0bH = $1be1
&bool r1bH = $1be3
&bool r2bH = $1be5
&bool r3bH = $1be7
&bool r4bH = $1be9
&bool r5bH = $1beb
&bool r6bH = $1bed
&bool r7bH = $1bef
&bool r8bH = $1bf1
&bool r9bH = $1bf3
&bool r10bH = $1bf5
&bool r11bH = $1bf7
&bool r12bH = $1bf9
&bool r13bH = $1bfb
&bool r14bH = $1bfd
&bool r15bH = $1bff
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31

View File

@ -439,12 +439,13 @@ sys {
const ubyte target = 64 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 5
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@ -968,6 +969,18 @@ _no_msb_size
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
@ -1006,6 +1019,7 @@ cx16 {
&uword r14 = $cffc
&uword r15 = $cffe
; signed word versions
&word r0s = $cfe0
&word r1s = $cfe2
&word r2s = $cfe4
@ -1023,6 +1037,7 @@ cx16 {
&word r14s = $cffc
&word r15s = $cffe
; ubyte versions (low and high bytes)
&ubyte r0L = $cfe0
&ubyte r1L = $cfe2
&ubyte r2L = $cfe4
@ -1057,6 +1072,7 @@ cx16 {
&ubyte r14H = $cffd
&ubyte r15H = $cfff
; signed byte versions (low and high bytes)
&byte r0sL = $cfe0
&byte r1sL = $cfe2
&byte r2sL = $cfe4
@ -1091,6 +1107,42 @@ cx16 {
&byte r14sH = $cffd
&byte r15sH = $cfff
; boolean versions
&bool r0bL = $cfe0
&bool r1bL = $cfe2
&bool r2bL = $cfe4
&bool r3bL = $cfe6
&bool r4bL = $cfe8
&bool r5bL = $cfea
&bool r6bL = $cfec
&bool r7bL = $cfee
&bool r8bL = $cff0
&bool r9bL = $cff2
&bool r10bL = $cff4
&bool r11bL = $cff6
&bool r12bL = $cff8
&bool r13bL = $cffa
&bool r14bL = $cffc
&bool r15bL = $cffe
&bool r0bH = $cfe1
&bool r1bH = $cfe3
&bool r2bH = $cfe5
&bool r3bH = $cfe7
&bool r4bH = $cfe9
&bool r5bH = $cfeb
&bool r6bH = $cfed
&bool r7bH = $cfef
&bool r8bH = $cff1
&bool r9bH = $cff3
&bool r10bH = $cff5
&bool r11bH = $cff7
&bool r12bH = $cff9
&bool r13bH = $cffb
&bool r14bH = $cffd
&bool r15bH = $cfff
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31

View File

@ -28,12 +28,20 @@
; 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.
; For now, you MUST call yield() only from the actual subroutine that has been registered as a task!
; (this is because otherwise the cpu call stack gets messed up and an RTS in task1 could suddenly pop a return address belonging to another tasks' call frame)
; - 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!
; - IMPORTANT: ``defer`` cannot be used inside a coroutine that is reused for multiple tasks!!!
;
; TIP: HOW TO WAIT without BLOCKING other coroutines?
; Make sure you call yield() in the waiting loop, for example:
; uword timer = cbm.RDTIM16() + 60
; while cbm.RDTIM16() != timer
; void coroutines.yield()
coroutines {
%option ignore_unused
@ -41,19 +49,17 @@ 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 {
sub add(uword @nozp taskaddress, uword @nozp 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
tasklist[cx16.r0L] = sys.get_as_returnaddress(taskaddress)
userdatas[cx16.r0L] = userdata
returnaddresses[cx16.r0L] = 0
sys.set_carry()
return cx16.r0L
}
@ -70,57 +76,48 @@ coroutines {
}
}
sub run(uword supervisor_routine) {
sub run(uword @nozp 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]
sys.pushw(tasklist[active_task])
return
}
}
}
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()
; Store the return address of the yielding task, and continue with the next one instead (round-robin)
; Returns the associated userdata value.
; NOTE: CAN ONLY BE CALLED FROM THE SCOPE OF THE SUBROUTINE THAT HAS BEEN REGISTERED AS THE TASK!
uword task_return_address
tasklist[active_task] = sys.popw()
resume_with_next_task:
skip_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 :)
return 0 ; exiting here will now actually return back to the calling program that called run()
}
if supervisor!=0 {
if supervisor!=0
if lsb(call(supervisor))==0
goto resume_with_next_task
}
goto skip_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
; returning from yield then continues with the next coroutine:
sys.pushw(task_return_address)
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)
if active_task==len(tasklist)
active_task=0
task_start = tasklist[active_task]
if task_start!=0 {
task_continue = returnaddresses[active_task]
task_return_address = tasklist[active_task]
if task_return_address!=0 {
return true
}
}
@ -128,9 +125,8 @@ resume_with_next_task:
}
}
sub kill(ubyte taskid) {
sub kill(ubyte @nozp taskid) {
tasklist[taskid] = 0
returnaddresses[taskid] = 0
}
sub current() -> ubyte {
@ -138,12 +134,10 @@ resume_with_next_task:
}
sub termination() {
; a task has terminated. wipe it from the list.
; this is an internal routine
; internal routine: a task has terminated. wipe it from the list.
kill(active_task)
; reactivate this termination handler
; note: cannot use pushw() because JSR doesn't push the return address in the same way
; reactivate this termination handler and go to the next task
sys.push_returnaddress(&termination)
goto coroutines.yield.resume_with_next_task
goto coroutines.yield.skip_task
}
}

View File

@ -22,18 +22,23 @@ monogfx {
const ubyte MODE_STIPPLE = %00000001
const ubyte MODE_INVERT = %00000010
uword buffer_visible, buffer_back
sub lores() {
; enable 320*240 bitmap mode
buffer_visible = buffer_back = $0000
cx16.VERA_CTRL=0
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = 0
cx16.VERA_L1_TILEBASE = 0 ; lores
width = 320
height = 240
lores_mode = true
buffer_visible = buffer_back = $0000
mode = MODE_NORMAL
clear_screen(false)
}
@ -46,14 +51,40 @@ monogfx {
cx16.VERA_DC_VSCALE = 128
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001
cx16.VERA_L1_TILEBASE = %00000001 ; hires
width = 640
height = 480
lores_mode = false
buffer_visible = buffer_back = $0000
mode = MODE_NORMAL
clear_screen(false)
}
sub enable_doublebuffer() {
; enable double buffering mode
if lores_mode {
buffer_visible = $0000
buffer_back = $2800
} else {
buffer_visible = $0000
buffer_back = $9800
}
}
sub swap_buffers(bool wait_for_vsync) {
; flip the buffers: make the back buffer visible and the other one now the backbuffer.
; to avoid any screen tearing it is advised to call this during the vertical blank (pass true)
if wait_for_vsync
sys.waitvsync()
cx16.r0 = buffer_back
buffer_back = buffer_visible
buffer_visible = cx16.r0
cx16.VERA_CTRL = 0
cx16.r0 &= %1111110000000000
cx16.VERA_L1_TILEBASE = cx16.VERA_L1_TILEBASE & 1 | (cx16.r0H >>1 )
}
sub textmode() {
; back to normal text mode
cx16.r15L = cx16.VERA_DC_VIDEO & %00000111 ; retain chroma + output mode
@ -559,6 +590,7 @@ drawmode: ora cx16.r15L
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
; Note: has problems with INVERT draw mode because of horizontal span overdrawing. Horizontal lines may occur.
if radius==0
return
ubyte @zp yy = 0
@ -597,6 +629,7 @@ drawmode: ora cx16.r15L
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
; Does bounds checking and clipping.
; Midpoint algorithm, filled
; Note: has problems with INVERT draw mode because of horizontal span overdrawing. Horizontal lines may occur.
if radius==0
return
ubyte @zp yy = 0
@ -672,8 +705,7 @@ nostipple:
invert:
prepare()
%asm {{
lda cx16.VERA_DATA0
eor p8v_maskbits,y
eor cx16.VERA_DATA0
sta cx16.VERA_DATA0
}}
return
@ -696,7 +728,7 @@ invert:
adc p8v_times40_lsb,y
sta cx16.VERA_ADDR_L
lda p8v_times40_msb,y
adc #0
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda p8v_xx
@ -708,18 +740,29 @@ invert:
; width=640 (hires)
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda p8v_xx
and #7
pha ; xbits
; xx /= 8
lsr p8v_xx+1
ror p8v_xx
lsr p8v_xx+1
ror p8v_xx
lsr p8v_xx
}}
xx /= 8
;xx /= 8
xx += yy*(640/8)
%asm {{
lda p8v_xx+1
sta cx16.VERA_ADDR_M
lda p8v_xx
sta cx16.VERA_ADDR_L
lda p8v_xx+1
clc
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #0
rol a ; hi bit carry also needed when double-buffering
sta cx16.VERA_ADDR_H
plx ; xbits
lda p8v_maskbits,x
}}
@ -761,11 +804,15 @@ invert:
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda p8v_xx+1
sta cx16.VERA_ADDR_M
lda p8v_xx
sta cx16.VERA_ADDR_L
lda p8v_xx+1
clc
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #0
rol a ; hi bit carry also needed when double-buffering
sta cx16.VERA_ADDR_H
ply ; xbits
lda p8s_plot.p8v_maskbits,y
and cx16.VERA_DATA0
@ -848,8 +895,8 @@ skip:
}
sub fill_scanline_right() {
; TODO maybe this could use vera auto increment, but that requires some clever masking calculations
cx16.r9s = xx
; TODO maybe this could use vera auto increment, but that requires some clever masking calculations
cx16.r9s = xx
while xx <= width-1 {
if pgetset()
break
@ -884,11 +931,15 @@ skip:
%asm {{
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
lda p8v_xpos+1
sta cx16.VERA_ADDR_M
lda p8v_xpos
sta cx16.VERA_ADDR_L
lda p8v_xpos+1
clc
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #0
rol a ; hi bit carry also needed when double-buffering
sta cx16.VERA_ADDR_H
ply ; xbits
lda p8s_plot.p8v_maskbits,y
and cx16.VERA_DATA0
@ -935,12 +986,12 @@ _doplot beq +
ror a
lsr a
lsr a
clc
ldy p8v_yy
clc
adc p8v_times40_lsb,y
sta cx16.VERA_ADDR_L
lda p8v_times40_msb,y
adc #0
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #%00010000 ; autoincr
sta cx16.VERA_ADDR_H
@ -948,7 +999,33 @@ _doplot beq +
}
else {
cx16.r0 = yy*(640/8)
cx16.vaddr(0, cx16.r0+(xx/8), 0, 1)
;cx16.r0 += xx/8
%asm {{
ldy p8v_xx+1
lda p8v_xx
sty P8ZP_SCRATCH_B1
lsr P8ZP_SCRATCH_B1
ror a
lsr P8ZP_SCRATCH_B1
ror a
lsr a
clc
adc cx16.r0
sta cx16.r0
bcc +
inc cx16.r0+1
+
stz cx16.VERA_CTRL
lda cx16.r0L
sta cx16.VERA_ADDR_L
lda cx16.r0H
clc
adc p8v_buffer_back+1
sta cx16.VERA_ADDR_M
lda #%00001000 ; autoincr (1 bit shifted)
rol a ; hi bit carry also needed when double-buffering
sta cx16.VERA_ADDR_H
}}
}
return
}
@ -1116,15 +1193,11 @@ cdraw_mod2 ora cx16.VERA_DATA1
cmp #0
beq +
lda #255
+ ldy #80
- sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
+ ldy #40
-
.rept 16
sta cx16.VERA_DATA0
.endrept
dey
bne -
rts

View File

@ -177,6 +177,7 @@ cx16 {
&uword r14 = $001e
&uword r15 = $0020
; signed word versions
&word r0s = $0002
&word r1s = $0004
&word r2s = $0006
@ -194,6 +195,7 @@ cx16 {
&word r14s = $001e
&word r15s = $0020
; ubyte versions (low and high bytes)
&ubyte r0L = $0002
&ubyte r1L = $0004
&ubyte r2L = $0006
@ -228,6 +230,7 @@ cx16 {
&ubyte r14H = $001f
&ubyte r15H = $0021
; signed byte versions (low and high bytes)
&byte r0sL = $0002
&byte r1sL = $0004
&byte r2sL = $0006
@ -262,6 +265,42 @@ cx16 {
&byte r14sH = $001f
&byte r15sH = $0021
; boolean versions
&bool r0bL = $0002
&bool r1bL = $0004
&bool r2bL = $0006
&bool r3bL = $0008
&bool r4bL = $000a
&bool r5bL = $000c
&bool r6bL = $000e
&bool r7bL = $0010
&bool r8bL = $0012
&bool r9bL = $0014
&bool r10bL = $0016
&bool r11bL = $0018
&bool r12bL = $001a
&bool r13bL = $001c
&bool r14bL = $001e
&bool r15bL = $0020
&bool r0bH = $0003
&bool r1bH = $0005
&bool r2bH = $0007
&bool r3bH = $0009
&bool r4bH = $000b
&bool r5bH = $000d
&bool r6bH = $000f
&bool r7bH = $0011
&bool r8bH = $0013
&bool r9bH = $0015
&bool r10bH = $0017
&bool r11bH = $0019
&bool r12bH = $001b
&bool r13bH = $001d
&bool r14bH = $001f
&bool r15bH = $0021
; VERA registers
const uword VERA_BASE = $9F20
@ -474,7 +513,7 @@ extsub $ff68 = mouse_config(byte shape @A, ubyte resX @X, ubyte resY @Y) clobbe
extsub $ff6b = mouse_get(ubyte zdataptr @X) -> ubyte @A, byte @X ; use mouse_pos() instead
extsub $ff71 = mouse_scan() clobbers(A, X, Y)
extsub $ff53 = joystick_scan() clobbers(A, X, Y)
extsub $ff56 = joystick_get(ubyte joynr @A) -> uword @AX, bool @Y ; note: everything is inverted
extsub $ff56 = joystick_get(ubyte joynr @A) -> uword @AX, bool @Y ; note: everything is inverted even the boolean present flag. Also see detect_joysticks() and get_all_joysticks()
; X16Edit (rom bank 13/14 but you ideally should use the routine search_x16edit() to search for the correct bank)
extsub $C000 = x16edit_default() clobbers(A,X,Y)
@ -1168,8 +1207,39 @@ asmsub restore_vera_context() clobbers(A) {
}}
}
sub joysticks_detect() -> ubyte {
; returns bits 0-4, set to 1 if that joystick is present.
; bit 0 = keyboard joystick, bit 1 - 4 = joypads 1 to 4
cx16.r0H = 255
for cx16.r0L in 4 downto 0 {
void cx16.joystick_get(cx16.r0L)
%asm {{
cpy #1 ; present?
}}
rol(cx16.r0H)
}
return ~cx16.r0H
}
; Commander X16 IRQ dispatcher routines
sub joysticks_getall(bool also_keyboard_js) -> uword {
; returns combined pressed buttons from all connected joysticks
; note: returns the 'normal' not inverted status bits for the buttons (1 = button pressed.)
cx16.r0H = 1
if also_keyboard_js
cx16.r0H = 0
cx16.r1 = $ffff
for cx16.r0L in cx16.r0H to 4 {
bool notpresent
cx16.r2, notpresent = cx16.joystick_get(cx16.r0L)
if not notpresent {
cx16.r1 &= cx16.r2
}
}
return ~cx16.r1
}
; Commander X16 IRQ dispatcher routines
inline asmsub disable_irqs() clobbers(A) {
; Disable all Vera IRQ sources. Note that it does NOT set the CPU IRQ disabled status bit!
@ -1216,6 +1286,7 @@ _vsync_vec .word ?
_line_vec .word ?
_aflow_vec .word ?
_sprcol_vec .word ?
_continue_with_system_handler .byte ?
.send BSS
_irq_dispatcher
@ -1224,44 +1295,42 @@ _irq_dispatcher
cld
lda cx16.VERA_ISR
and cx16.VERA_IEN ; only consider the bits for sources that can actually raise the IRQ
sta cx16.VERA_ISR ; note: AFLOW can only be cleared by filling the audio FIFO for at least 1/4. Not via the ISR bit.
bit #2
stz _continue_with_system_handler
bit #2 ; make sure to test for LINE IRQ first to handle that as soon as we can
beq +
pha
jsr _line_handler
ldy #2
sty cx16.VERA_ISR
bra _dispatch_end
+
bit #4
beq +
jsr _sprcol_handler
ldy #4
sty cx16.VERA_ISR
bra _dispatch_end
+
bit #8
beq +
jsr _aflow_handler
; note: AFLOW can only be cleared by filling the audio FIFO for at least 1/4. Not via the ISR bit.
bra _dispatch_end
+
bit #1
beq +
tsb _continue_with_system_handler
pla
+ lsr a
bcc +
pha
jsr _vsync_handler
cmp #0
bne _dispatch_end
ldy #1
sty cx16.VERA_ISR
bra _return_irq
+
lda #0
_dispatch_end
cmp #0
beq _return_irq
jsr sys.restore_prog8_internals
tsb _continue_with_system_handler
pla
+ lsr a
lsr a
bcc +
pha
jsr _sprcol_handler
tsb _continue_with_system_handler
pla
+ lsr a
bcc +
jsr _aflow_handler
tsb _continue_with_system_handler
+ jsr sys.restore_prog8_internals
lda _continue_with_system_handler
beq _no_sys_handler
jmp (sys.restore_irq._orig_irqvec) ; continue with normal kernal irq routine
_return_irq
jsr sys.restore_prog8_internals
_no_sys_handler
ply
plx
pla
@ -1485,12 +1554,13 @@ sys {
const ubyte target = 16 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 5
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@ -1997,6 +2067,18 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla

View File

@ -241,6 +241,28 @@ divmod_ub_asm .proc
rts
.pend
remainder_ub_asm .proc
; -- divide A by Y, returns remainder in A (unsigned)
; division by zero will result in just the original number.
; This routine specialcases 0,1,2 and otherwise is just a repeated subtraction.
cpy #0
beq _zero
cpy #1
bne +
lda #0
rts
+ cpy #2
bne +
and #1
rts
+ sty P8ZP_SCRATCH_REG
sec
- sbc P8ZP_SCRATCH_REG
bcs -
adc P8ZP_SCRATCH_REG
_zero rts
.pend
divmod_w_asm .proc
; signed word division: make everything positive and fix sign afterwards
sta P8ZP_SCRATCH_W2

View File

@ -697,25 +697,36 @@ log2_tab
; Linear interpolation (LERP)
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 255]
; guarantees v = v1 when t = 255
return v0 + msb(t as uword * (v1 - v0) + 255)
if v1<v0
return v0 - msb(t as uword * (v0 - v1) + 255)
else
return v0 + msb(t as uword * (v1 - v0) + 255)
}
sub lerpw(uword v0, uword v1, uword t) -> uword {
; Linear interpolation (LERP) on word values
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 65535]
; guarantees v = v1 when t = 65535
; guarantees v = v1 when t = 65535. Clobbers R15.
if v1<v0 {
t *= v0-v1
cx16.r15 = math.mul16_last_upper()
if t!=0
cx16.r15++
return v0 - cx16.r15
}
t *= v1-v0
cx16.r0 = math.mul16_last_upper()
cx16.r15 = math.mul16_last_upper()
if t!=0
cx16.r0++
return v0 + cx16.r0
cx16.r15++
return v0 + cx16.r15
}
sub interpolate(ubyte v, ubyte inputMin, ubyte inputMax, ubyte outputMin, ubyte outputMax) -> ubyte {
; Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax]
; There is no version for words because of lack of precision in the fixed point calculation there.
cx16.r0 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin)
cx16.r0 *= (outputMax-outputMin)
return cx16.r0H + outputMin
; Clobbers R15.
; (There is no version for words because of lack of precision in the fixed point calculation there)
cx16.r15 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin)
cx16.r15 *= (outputMax-outputMin)
return cx16.r15H + outputMin
}
}

View File

@ -98,12 +98,13 @@ sys {
const ubyte target = 32 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 0 ; undefined, no floats supported
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@ -481,6 +482,18 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
@ -519,6 +532,7 @@ cx16 {
&uword r14 = $7ffc
&uword r15 = $7ffe
; signed word versions
&word r0s = $7fe0
&word r1s = $7fe2
&word r2s = $7fe4
@ -536,6 +550,7 @@ cx16 {
&word r14s = $7ffc
&word r15s = $7ffe
; ubyte versions (low and high bytes)
&ubyte r0L = $7fe0
&ubyte r1L = $7fe2
&ubyte r2L = $7fe4
@ -570,6 +585,7 @@ cx16 {
&ubyte r14H = $7ffd
&ubyte r15H = $7fff
; signed byte versions (low and high bytes)
&byte r0sL = $7fe0
&byte r1sL = $7fe2
&byte r2sL = $7fe4
@ -604,6 +620,42 @@ cx16 {
&byte r14sH = $7ffd
&byte r15sH = $7fff
; boolean versions
&bool r0bL = $7fe0
&bool r1bL = $7fe2
&bool r2bL = $7fe4
&bool r3bL = $7fe6
&bool r4bL = $7fe8
&bool r5bL = $7fea
&bool r6bL = $7fec
&bool r7bL = $7fee
&bool r8bL = $7ff0
&bool r9bL = $7ff2
&bool r10bL = $7ff4
&bool r11bL = $7ff6
&bool r12bL = $7ff8
&bool r13bL = $7ffa
&bool r14bL = $7ffc
&bool r15bL = $7ffe
&bool r0bH = $7fe1
&bool r1bH = $7fe3
&bool r2bH = $7fe5
&bool r3bH = $7fe7
&bool r4bH = $7fe9
&bool r5bH = $7feb
&bool r6bH = $7fed
&bool r7bH = $7fef
&bool r8bH = $7ff1
&bool r9bH = $7ff3
&bool r10bH = $7ff5
&bool r11bH = $7ff7
&bool r12bH = $7ff9
&bool r13bH = $7ffb
&bool r14bH = $7ffd
&bool r15bH = $7fff
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31

View File

@ -43,46 +43,72 @@ _done
}}
}
/*
prog8 source code for the above routine:
sub gnomesort_ub(uword @requirezp values, ubyte num_elements) {
sub gnomesort_by_ub(uword @requirezp uw_keys, uword values, ubyte num_elements) {
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
ubyte @zp pos=1
while pos != num_elements {
if values[pos]>=values[pos-1]
if uw_keys[pos]>=uw_keys[pos-1]
pos++
else {
; swap elements
cx16.r0L = values[pos-1]
values[pos-1] = values[pos]
values[pos] = cx16.r0L
cx16.r0L = uw_keys[pos-1]
uw_keys[pos-1] = uw_keys[pos]
uw_keys[pos] = cx16.r0L
uword @requirezp vptr = values + pos*$0002 -2
cx16.r0 = peekw(vptr)
pokew(vptr, peekw(vptr+2))
pokew(vptr+2, cx16.r0)
pos--
if_z
pos++
}
}
}
*/
sub gnomesort_uw(uword values, ubyte num_elements) {
; TODO optimize this more, rewrite in asm?
ubyte @zp pos = 1
uword @requirezp ptr = values+2
sub gnomesort_uw(uword @requirezp values, ubyte num_elements) {
; Sorts the values array (no-split unsigned words).
; Max number of elements is 128. Clobbers R0 and R1.
ubyte @zp pos=2
num_elements *= 2
while pos != num_elements {
cx16.r0 = peekw(ptr-2)
cx16.r1 = peekw(ptr)
if cx16.r0<=cx16.r1 {
pos++
ptr+=2
}
cx16.r1L = pos-2
if peekw(values+pos) >= peekw(values + cx16.r1L)
pos += 2
else {
; swap elements
pokew(ptr-2, cx16.r1)
pokew(ptr, cx16.r0)
if pos>1 {
pos--
ptr-=2
}
cx16.r0 = peekw(values + cx16.r1L)
pokew(values + cx16.r1L, peekw(values + pos))
pokew(values + pos, cx16.r0)
pos-=2
if_z
pos+=2
}
}
}
sub gnomesort_by_uw(uword @requirezp uw_keys, uword wordvalues, ubyte num_elements) {
; Sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
; both arrays should be no-split array of words. uw_keys are unsigned.
; Max number of elements is 128. Clobbers R0 and R1.
ubyte @zp pos=2
num_elements *= 2
while pos != num_elements {
cx16.r1L = pos-2
if peekw(uw_keys+pos) >= peekw(uw_keys + cx16.r1L)
pos += 2
else {
; swap elements
cx16.r0 = peekw(uw_keys + cx16.r1L)
pokew(uw_keys + cx16.r1L, peekw(uw_keys+ pos))
pokew(uw_keys + pos, cx16.r0)
cx16.r0 = peekw(wordvalues + cx16.r1L)
pokew(wordvalues + cx16.r1L, peekw(wordvalues + pos))
pokew(wordvalues + pos, cx16.r0)
pos-=2
if_z
pos+=2
}
}
}
@ -90,6 +116,7 @@ _done
; gnomesort_pointers is not worth it over shellshort_pointers.
sub shellsort_ub(uword @requirezp values, ubyte num_elements) {
; sorts the values array (unsigned bytes).
num_elements--
ubyte @zp gap
for gap in [132, 57, 23, 10, 4, 1] {
@ -112,6 +139,7 @@ _done
}
sub shellsort_uw(uword @requirezp values, ubyte num_elements) {
; sorts the values array (no-split unsigned words).
num_elements--
ubyte gap
for gap in [132, 57, 23, 10, 4, 1] {
@ -121,13 +149,65 @@ _done
ubyte @zp j = i
ubyte @zp k = j-gap
while j>=gap {
uword @zp v = peekw(values+k*2)
uword @zp v = peekw(values+k*$0002)
if v <= temp break
pokew(values+j*2, v)
pokew(values+j*$0002, v)
j = k
k -= gap
}
pokew(values+j*2, temp)
pokew(values+j*$0002, temp)
}
}
}
sub shellsort_by_ub(uword @requirezp ub_keys, uword @requirezp wordvalues, ubyte num_elements) {
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
num_elements--
ubyte @zp gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
ubyte @zp temp = ub_keys[i]
uword temp_wv = peekw(wordvalues + i*$0002)
ubyte @zp j = i
ubyte @zp k = j-gap
repeat {
ubyte @zp v = ub_keys[k]
if v <= temp break
if j < gap break
ub_keys[j] = v
pokew(wordvalues + j*$0002, peekw(wordvalues + k*$0002))
j = k
k -= gap
}
ub_keys[j] = temp
pokew(wordvalues + j*$0002, temp_wv)
}
}
}
sub shellsort_by_uw(uword @requirezp uw_keys, uword @requirezp wordvalues, ubyte num_elements) {
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
; both arrays should be no-split array of words. uw_keys are unsigned.
num_elements--
ubyte gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
uword @zp temp = peekw(uw_keys+i*$0002)
uword temp_wv = peekw(wordvalues + i*$0002)
ubyte @zp j = i
ubyte @zp k = j-gap
while j>=gap {
uword @zp v = peekw(uw_keys+k*2)
if v <= temp break
pokew(uw_keys+j*2, v)
pokew(wordvalues + j*$0002, peekw(wordvalues + k*$0002))
j = k
k -= gap
}
pokew(uw_keys+j*2, temp)
pokew(wordvalues + j*$0002, temp_wv)
}
}
}
@ -144,14 +224,14 @@ _done
ubyte @zp j = i
ubyte @zp k = j-gap
while j>=gap {
cx16.r0 = peekw(pointers+k*2)
cx16.r0 = peekw(pointers+k*$0002)
void call(comparefunc)
if_cs break
pokew(pointers+j*2, cx16.r0)
pokew(pointers+j*$0002, cx16.r0)
j = k
k -= gap
}
pokew(pointers+j*2, cx16.r1)
pokew(pointers+j*$0002, cx16.r1)
}
}
}

View File

@ -151,6 +151,33 @@ _found tya
}}
}
asmsub find_eol(uword string @AY) -> ubyte @A, bool @Pc {
; Locates the position of the first End Of Line character in the string.
; This is a convenience function that looks for both a CR or LF (byte 13 or byte 10) as being a possible Line Ending.
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
%asm {{
; need to copy the the cx16 virtual registers to zeropage to make this run on C64...
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq _notfound
cmp #13
beq _found
cmp #10
beq _found
iny
bne -
_notfound lda #255
clc
rts
_found tya
sec
rts
}}
}
asmsub rfind(uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc {
; Locates the first position of the given character in the string, starting from the right.
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).

View File

@ -404,25 +404,36 @@ math {
; Linear interpolation (LERP)
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 255]
; guarantees v = v1 when t = 255
return v0 + msb(t as uword * (v1 - v0) + 255)
if v1<v0
return v0 - msb(t as uword * (v0 - v1) + 255)
else
return v0 + msb(t as uword * (v1 - v0) + 255)
}
sub lerpw(uword v0, uword v1, uword t) -> uword {
; Linear interpolation (LERP) on word values
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 65535]
; guarantees v = v1 when t = 65535
; guarantees v = v1 when t = 65535. Clobbers R15.
if v1<v0 {
t *= v0-v1
cx16.r15 = math.mul16_last_upper()
if t!=0
cx16.r15++
return v0 - cx16.r15
}
t *= v1-v0
cx16.r0 = math.mul16_last_upper()
cx16.r15 = math.mul16_last_upper()
if t!=0
cx16.r0++
return v0 + cx16.r0
cx16.r15++
return v0 + cx16.r15
}
sub interpolate(ubyte v, ubyte inputMin, ubyte inputMax, ubyte outputMin, ubyte outputMax) -> ubyte {
; Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax]
; There is no version for words because of lack of precision in the fixed point calculation there.
cx16.r0 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin)
cx16.r0 *= (outputMax-outputMin)
return cx16.r0H + outputMin
; Clobbers R15.
; (There is no version for words because of lack of precision in the fixed point calculation there)
cx16.r15 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin)
cx16.r15 *= (outputMax-outputMin)
return cx16.r15H + outputMin
}
}

View File

@ -0,0 +1,103 @@
; **experimental** data sorting routines, API subject to change!!
; NOTE: gnomesort is not implemented here, just use shellshort.
sorting {
%option ignore_unused
sub shellsort_ub(uword @requirezp values, ubyte num_elements) {
num_elements--
ubyte @zp gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
ubyte @zp temp = values[i]
ubyte @zp j = i
ubyte @zp k = j-gap
repeat {
ubyte @zp v = values[k]
if v <= temp break
if j < gap break
values[j] = v
j = k
k -= gap
}
values[j] = temp
}
}
}
sub shellsort_uw(uword @requirezp values, ubyte num_elements) {
num_elements--
ubyte gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
uword @zp temp = peekw(values+i*$0002)
ubyte @zp j = i
ubyte @zp k = j-gap
while j>=gap {
uword @zp v = peekw(values+k*2)
if v <= temp break
pokew(values+j*2, v)
j = k
k -= gap
}
pokew(values+j*2, temp)
}
}
}
sub shellsort_by_ub(uword @requirezp ub_keys, uword @requirezp wordvalues, ubyte num_elements) {
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
num_elements--
ubyte @zp gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
ubyte @zp temp = ub_keys[i]
uword temp_wv = peekw(wordvalues + i*$0002)
ubyte @zp j = i
ubyte @zp k = j-gap
repeat {
ubyte @zp v = ub_keys[k]
if v <= temp break
if j < gap break
ub_keys[j] = v
pokew(wordvalues + j*$0002, peekw(wordvalues + k*$0002))
j = k
k -= gap
}
ub_keys[j] = temp
pokew(wordvalues + j*$0002, temp_wv)
}
}
}
sub shellsort_by_uw(uword @requirezp uw_keys, uword @requirezp wordvalues, ubyte num_elements) {
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
; both arrays should be no-split array of words. uw_keys are unsigned.
num_elements--
ubyte gap
for gap in [132, 57, 23, 10, 4, 1] {
ubyte i
for i in gap to num_elements {
uword @zp temp = peekw(uw_keys+i*$0002)
uword temp_wv = peekw(wordvalues + i*$0002)
ubyte @zp j = i
ubyte @zp k = j-gap
while j>=gap {
uword @zp v = peekw(uw_keys+k*2)
if v <= temp break
pokew(uw_keys+j*2, v)
pokew(wordvalues + j*$0002, peekw(wordvalues + k*$0002))
j = k
k -= gap
}
pokew(uw_keys+j*2, temp)
pokew(wordvalues + j*$0002, temp_wv)
}
}
}
}

View File

@ -53,34 +53,47 @@ strings {
target[ix]=0
}
sub find(str st, ubyte character) -> ubyte {
sub find(str st, ubyte character) -> ubyte, bool {
; Locates the first position of the given character in the string,
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
; NOTE: because this isn't an asmsub, there's only a SINGLE return value here. On the c64/cx16 targets etc there are 2 return values.
; returns index in A, and boolean if found or not. (when false A will also be 255, an invalid index).
ubyte ix
for ix in 0 to length(st)-1 {
if st[ix]==character {
sys.set_carry()
return ix
return ix, true
}
}
sys.clear_carry()
return 255
return 255, false
}
sub rfind(uword stringptr, ubyte character) -> ubyte {
sub find_eol(str st) -> ubyte, bool {
; Locates the position of the first End Of Line character in the string.
; This is a convenience function that looks for both a CR or LF (byte 13 or byte 10) as being a possible Line Ending.
; returns index in A, and boolean if found or not. (when false A will also be 255, an invalid index).
ubyte ix
for ix in 0 to length(st)-1 {
if st[ix] in "\x0a\x0d" {
sys.set_carry()
return ix, true
}
}
sys.clear_carry()
return 255, false
}
sub rfind(uword stringptr, ubyte character) -> ubyte, bool {
; Locates the first position of the given character in the string, starting from the right.
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
; NOTE: because this isn't an asmsub, there's only a SINGLE return value here. On the c64/cx16 targets etc there are 2 return values.
; returns index in A, and boolean if found or not. (when false A will also be 255, an invalid index).
ubyte ix
for ix in length(stringptr)-1 downto 0 {
if stringptr[ix]==character {
sys.set_carry()
return ix
return ix, true
}
}
sys.clear_carry()
return 255
return 255, false
}
sub contains(str st, ubyte character) -> bool {

View File

@ -7,12 +7,13 @@ sys {
const ubyte target = 255 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 8
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_FLOAT = sizeof(float)
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@ -198,6 +199,12 @@ sys {
}}
}
sub get_as_returnaddress(uword address) -> uword {
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
address--
return mkword(lsb(address), msb(address))
}
sub pop() -> ubyte {
; note: this *should* be inlined, however since the VM has separate program counter and value stacks, this also works
%ir {{
@ -231,9 +238,8 @@ sys {
if_cs
cx16.r0L |= 1
; TODO: overflow flag not yet supported
; if_vs
; cx16.r0L |= %01000000
if_vs
cx16.r0L |= %01000000
return cx16.r0L
}
@ -260,6 +266,7 @@ cx16 {
&uword r14 = $ff1e
&uword r15 = $ff20
; signed word versions
&word r0s = $ff02
&word r1s = $ff04
&word r2s = $ff06
@ -277,6 +284,7 @@ cx16 {
&word r14s = $ff1e
&word r15s = $ff20
; ubyte versions (low and high bytes)
&ubyte r0L = $ff02
&ubyte r1L = $ff04
&ubyte r2L = $ff06
@ -311,6 +319,7 @@ cx16 {
&ubyte r14H = $ff1f
&ubyte r15H = $ff21
; signed byte versions (low and high bytes)
&byte r0sL = $ff02
&byte r1sL = $ff04
&byte r2sL = $ff06
@ -345,6 +354,42 @@ cx16 {
&byte r14sH = $ff1f
&byte r15sH = $ff21
; boolean versions
&bool r0bL = $ff02
&bool r1bL = $ff04
&bool r2bL = $ff06
&bool r3bL = $ff08
&bool r4bL = $ff0a
&bool r5bL = $ff0c
&bool r6bL = $ff0e
&bool r7bL = $ff10
&bool r8bL = $ff12
&bool r9bL = $ff14
&bool r10bL = $ff16
&bool r11bL = $ff18
&bool r12bL = $ff1a
&bool r13bL = $ff1c
&bool r14bL = $ff1e
&bool r15bL = $ff20
&bool r0bH = $ff03
&bool r1bH = $ff05
&bool r2bH = $ff07
&bool r3bH = $ff09
&bool r4bH = $ff0b
&bool r5bH = $ff0d
&bool r6bH = $ff0f
&bool r7bH = $ff11
&bool r8bH = $ff13
&bool r9bH = $ff15
&bool r10bH = $ff17
&bool r11bH = $ff19
&bool r12bH = $ff1b
&bool r13bH = $ff1d
&bool r14bH = $ff1f
&bool r15bH = $ff21
sub save_virtual_registers() {
uword[32] storage
storage[0] = r0

View File

@ -65,6 +65,7 @@ private fun compileMain(args: Array<String>): Boolean {
val dontSplitWordArrays by cli.option(ArgType.Boolean, fullName = "dontsplitarrays", description = "don't store any word array as split lsb/msb in memory, as if all of those have @nosplit")
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of ${CompilationTargets.joinToString(",")} or a custom target properties file) (required)")
val showTimings by cli.option(ArgType.Boolean, fullName = "timings", description = "show internal compiler timings (for performance analysis)")
val varsGolden by cli.option(ArgType.Boolean, fullName = "varsgolden", description = "put uninitialized variables in 'golden ram' memory area instead of at the end of the program. On the cx16 target this is $0400-07ff. This is unavailable on other systems.")
val varsHighBank by cli.option(ArgType.Int, fullName = "varshigh", description = "put uninitialized variables in high memory area instead of at the end of the program. On the cx16 target the value specifies the HiRAM bank to use, on other systems this value is ignored.")
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "run a .p8ir IR source file in the embedded VM")
@ -181,6 +182,7 @@ private fun compileMain(args: Array<String>): Boolean {
warnSymbolShadowing == true,
quietAll == true,
quietAll == true || quietAssembler == true,
showTimings == true,
asmListfile == true,
dontIncludeSourcelines != true,
experimentalCodegen == true,
@ -265,6 +267,7 @@ private fun compileMain(args: Array<String>): Boolean {
warnSymbolShadowing == true,
quietAll == true,
quietAll == true || quietAssembler == true,
showTimings == true,
asmListfile == true,
dontIncludeSourcelines != true,
experimentalCodegen == true,

View File

@ -115,6 +115,19 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
else -> NumericLiteral(BaseDataType.UBYTE, program.memsizer.memorySize(dt.getOrUndef(), null).toDouble(), position)
}
} else {
val identifier = args[0] as? IdentifierReference
if(identifier?.nameInSource?.size==1) {
when(identifier.nameInSource[0]) {
"ubyte" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.UBYTE), position)
"byte" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.BYTE), position)
"uword" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.UWORD), position)
"word" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.WORD), position)
"long" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.LONG), position)
"float" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.FLOAT), position)
"bool" -> return NumericLiteral.optimalInteger(program.memsizer.memorySize(BaseDataType.BOOL), position)
}
}
throw SyntaxError("sizeof invalid argument type", position)
}
}

View File

@ -24,9 +24,11 @@ import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
import kotlin.io.path.nameWithoutExtension
import kotlin.math.round
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
import kotlin.time.Duration
import kotlin.time.DurationUnit
import kotlin.time.measureTime
import kotlin.time.measureTimedValue
class CompilationResult(val compilerAst: Program, // deprecated, use codegenAst instead
@ -40,6 +42,7 @@ class CompilerArguments(val filepath: Path,
val warnSymbolShadowing: Boolean,
val quietAll: Boolean,
val quietAssembler: Boolean,
val showTimings: Boolean,
val asmListfile: Boolean,
val includeSourcelines: Boolean,
val experimentalCodegen: Boolean,
@ -83,9 +86,20 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
}
try {
val totalTime = measureTimeMillis {
val totalTime = measureTime {
val libraryDirs = if(compTarget.libraryPath!=null) listOf(compTarget.libraryPath.toString()) else emptyList()
val (program, options, imported) = parseMainModule(args.filepath, args.errors, compTarget, args.sourceDirs, libraryDirs, args.quietAll)
val (parseresult, parseDuration) = measureTimedValue {
parseMainModule(
args.filepath,
args.errors,
compTarget,
args.sourceDirs,
libraryDirs,
args.quietAll
)
}
val (program, options, imported) = parseresult
compilationOptions = options
with(compilationOptions) {
@ -120,83 +134,115 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
}
processAst(program, args.errors, compilationOptions)
val processDuration = measureTime {
processAst(program, args.errors, compilationOptions)
}
// println("*********** COMPILER AST RIGHT BEFORE OPTIMIZING *************")
// printProgram(program)
if (compilationOptions.optimize) {
optimizeAst(
program,
compilationOptions,
args.errors,
BuiltinFunctionsFacade(BuiltinFunctions),
)
val optimizeDuration = measureTime {
if (compilationOptions.optimize) {
optimizeAst(
program,
compilationOptions,
args.errors,
BuiltinFunctionsFacade(BuiltinFunctions),
)
}
}
determineProgramLoadAddress(program, compilationOptions, args.errors)
args.errors.report()
postprocessAst(program, args.errors, compilationOptions)
args.errors.report()
val postprocessDuration = measureTime {
determineProgramLoadAddress(program, compilationOptions, args.errors)
args.errors.report()
postprocessAst(program, args.errors, compilationOptions)
args.errors.report()
}
// println("*********** COMPILER AST BEFORE ASSEMBLYGEN *************")
// printProgram(program)
var createAssemblyDuration = Duration.ZERO
var simplifiedAstDuration = Duration.ZERO
if (args.writeAssembly) {
// re-initialize memory areas with final compilationOptions
compilationOptions.compTarget.initializeMemoryAreas(compilationOptions)
program.processAstBeforeAsmGeneration(compilationOptions, args.errors)
args.errors.report()
if(args.printAst1) {
if (args.printAst1) {
println("\n*********** COMPILER AST *************")
printProgram(program)
println("*********** COMPILER AST END *************\n")
}
val intermediateAst = SimplifiedAstMaker(program, args.errors).transform()
val stMaker = SymbolTableMaker(intermediateAst, compilationOptions)
val symbolTable = stMaker.make()
val (intermediateAst, simplifiedAstDuration2) = measureTimedValue {
val intermediateAst = SimplifiedAstMaker(program, args.errors).transform()
val stMaker = SymbolTableMaker(intermediateAst, compilationOptions)
val symbolTable = stMaker.make()
postprocessSimplifiedAst(intermediateAst, symbolTable, args.errors)
args.errors.report()
if(compilationOptions.optimize) {
optimizeSimplifiedAst(intermediateAst, compilationOptions, symbolTable, args.errors)
postprocessSimplifiedAst(intermediateAst, symbolTable, args.errors)
args.errors.report()
if (compilationOptions.optimize) {
optimizeSimplifiedAst(intermediateAst, compilationOptions, symbolTable, args.errors)
args.errors.report()
}
if (args.printAst2) {
println("\n*********** SIMPLIFIED AST *************")
printAst(intermediateAst, true, ::println)
println("*********** SIMPLIFIED AST END *************\n")
}
verifyFinalAstBeforeAsmGen(intermediateAst, compilationOptions, symbolTable, args.errors)
args.errors.report()
intermediateAst
}
simplifiedAstDuration =simplifiedAstDuration2
if(args.printAst2) {
println("\n*********** SIMPLIFIED AST *************")
printAst(intermediateAst, true, ::println)
println("*********** SIMPLIFIED AST END *************\n")
}
verifyFinalAstBeforeAsmGen(intermediateAst, compilationOptions, symbolTable, args.errors)
args.errors.report()
if(!createAssemblyAndAssemble(intermediateAst, args.errors, compilationOptions, program.generatedLabelSequenceNumber)) {
System.err.println("Error in codegeneration or assembler")
return null
createAssemblyDuration = measureTime {
if (!createAssemblyAndAssemble(
intermediateAst,
args.errors,
compilationOptions,
program.generatedLabelSequenceNumber
)
) {
System.err.println("Error in codegeneration or assembler")
return null
}
}
ast = intermediateAst
} else {
if(args.printAst1) {
if (args.printAst1) {
println("\n*********** COMPILER AST *************")
printProgram(program)
println("*********** COMPILER AST END *************\n")
}
if(args.printAst2) {
if (args.printAst2) {
System.err.println("There is no simplified Ast available if assembly generation is disabled.")
}
}
System.out.flush()
System.err.flush()
if(!args.quietAll && args.showTimings) {
println("\n**** TIMINGS ****")
println("source parsing : ${parseDuration.toString(DurationUnit.SECONDS, 3)}")
println("ast processing : ${processDuration.toString(DurationUnit.SECONDS, 3)}")
println("ast optimizing : ${optimizeDuration.toString(DurationUnit.SECONDS, 3)}")
println("ast postprocess : ${postprocessDuration.toString(DurationUnit.SECONDS, 3)}")
println("code prepare : ${simplifiedAstDuration.toString(DurationUnit.SECONDS, 3)}")
println("code generation : ${createAssemblyDuration.toString(DurationUnit.SECONDS, 3)}")
val totalDuration = parseDuration + processDuration + optimizeDuration + postprocessDuration + simplifiedAstDuration + createAssemblyDuration
println(" total : ${totalDuration.toString(DurationUnit.SECONDS, 3)}")
}
}
System.out.flush()
System.err.flush()
if(!args.quietAll) {
val seconds = totalTime / 1000.0
println("\nTotal compilation+assemble time: ${round(seconds * 100.0) / 100.0} sec.")
println("\nTotal compilation+assemble time: ${totalTime.toString(DurationUnit.SECONDS, 3)}.")
}
return CompilationResult(resultingProgram!!, ast, compilationOptions, importedFiles)
} catch (px: ParseError) {
@ -424,7 +470,7 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
errors.report()
program.reorderStatements(errors)
errors.report()
program.desugaring(errors)
program.desugaring(errors, compilerOptions)
errors.report()
program.changeNotExpressionAndIfComparisonExpr(errors, compilerOptions.compTarget)
errors.report()
@ -486,7 +532,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
}
private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
program.desugaring(errors)
program.desugaring(errors, compilerOptions)
program.addTypecasts(errors, compilerOptions)
errors.report()
program.variousCleanups(errors, compilerOptions)
@ -495,8 +541,21 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
program.verifyFunctionArgTypes(errors, compilerOptions)
errors.report()
program.moveMainBlockAsFirst(compilerOptions.compTarget)
val fixer = BeforeAsmAstChanger(program, compilerOptions, errors)
fixer.visit(program)
while (errors.noErrors() && fixer.applyModifications() > 0) {
fixer.visit(program)
}
program.checkValid(errors, compilerOptions) // check if final tree is still valid
errors.report()
val cleaner = BeforeAsmTypecastCleaner(program, errors)
cleaner.visit(program)
while (errors.noErrors() && cleaner.applyModifications() > 0) {
cleaner.visit(program)
}
}
private fun createAssemblyAndAssemble(program: PtProgram,

View File

@ -5,7 +5,9 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import prog8.code.target.C128Target
import prog8.code.target.Cx16Target
import prog8.code.target.PETTarget
import prog8.code.target.VMTarget
import prog8.compiler.builtinFunctionReturnType
import java.io.CharConversionException
@ -182,8 +184,6 @@ internal class AstChecker(private val program: Program,
val iterableDt = forLoop.iterable.inferType(program).getOrUndef()
if(iterableDt.isNumeric) TODO("iterable type should not be simple numeric!? "+forLoop.position)
if(forLoop.iterable is IFunctionCall) {
errors.err("can not loop over function call return value", forLoop.position)
} else if(!(iterableDt.isIterable) && forLoop.iterable !is RangeExpression) {
@ -196,7 +196,7 @@ internal class AstChecker(private val program: Program,
require(loopvar.datatype.isNumericOrBool)
when (loopvar.datatype.base) {
BaseDataType.UBYTE -> {
if(!iterableDt.isUnsignedByte && !iterableDt.isUnsignedByteArray && !iterableDt.isString) // TODO remove ubyte check?
if(!iterableDt.isUnsignedByte && !iterableDt.isUnsignedByteArray && !iterableDt.isString)
errors.err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position)
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpression)
}
@ -205,7 +205,7 @@ internal class AstChecker(private val program: Program,
errors.err("bool loop variable can only loop over boolean array", forLoop.position)
}
BaseDataType.UWORD -> {
if(!iterableDt.isUnsignedByte && !iterableDt.isUnsignedWord && !iterableDt.isString && // TODO remove byte and word check?
if(!iterableDt.isUnsignedByte && !iterableDt.isUnsignedWord && !iterableDt.isString &&
!iterableDt.isUnsignedByteArray && !iterableDt.isUnsignedWordArray &&
!iterableDt.isSplitWordArray)
errors.err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position)
@ -213,7 +213,7 @@ internal class AstChecker(private val program: Program,
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpression)
}
BaseDataType.BYTE -> {
if(!iterableDt.isSignedByte && !iterableDt.isSignedByteArray) // TODO remove byte check?
if(!iterableDt.isSignedByte && !iterableDt.isSignedByteArray)
errors.err("byte loop variable can only loop over bytes", forLoop.position)
}
BaseDataType.WORD -> {
@ -406,11 +406,42 @@ internal class AstChecker(private val program: Program,
// subroutine must contain at least one 'return' or 'goto'
// (or if it has an asm block, that must contain a 'rts' or 'jmp' or 'bra')
var haveReturnError = false
if(!hasReturnOrExternalJumpOrRts(subroutine)) {
if (subroutine.returntypes.isNotEmpty()) {
// for asm subroutines with an address, no statement check is possible.
if (subroutine.asmAddress == null && !subroutine.inline)
if (subroutine.asmAddress == null && !subroutine.inline) {
err("non-inline subroutine has result value(s) and thus must have at least one 'return' or external 'goto' in it (or the assembler equivalent in case of %asm)")
haveReturnError = true
}
}
}
val lastStatement = subroutine.statements.reversed().dropWhile { it is Subroutine || it is VarDecl || it is Directive || (it is Assignment && it.origin== AssignmentOrigin.VARINIT) }.firstOrNull()
if(!haveReturnError && !subroutine.isAsmSubroutine && !subroutine.inline && subroutine.returntypes.isNotEmpty() && lastStatement !is Return) {
if(lastStatement==null)
err("subroutine '${subroutine.name}' has result value(s) but doesn't end with a return statement")
else {
val returnError = when (lastStatement) {
is Jump, is OnGoto -> false
is IStatementContainer -> !hasReturnOrExternalJumpOrRts(lastStatement as IStatementContainer)
is InlineAssembly -> !lastStatement.hasReturnOrRts()
is ForLoop -> hasReturnOrExternalJumpOrRts(lastStatement.body)
is IfElse -> !hasReturnOrExternalJumpOrRts(lastStatement.truepart) && !hasReturnOrExternalJumpOrRts(lastStatement.elsepart)
is ConditionalBranch -> !hasReturnOrExternalJumpOrRts(lastStatement.truepart) && !hasReturnOrExternalJumpOrRts(lastStatement.elsepart)
is RepeatLoop -> {
lastStatement.iterations!=null || !hasReturnOrExternalJumpOrRts(lastStatement.body)
}
is UntilLoop -> !hasReturnOrExternalJumpOrRts(lastStatement.body)
is WhileLoop -> !hasReturnOrExternalJumpOrRts(lastStatement.body)
is When -> lastStatement.choices.all { !hasReturnOrExternalJumpOrRts(it.statements) }
else -> true
}
if(returnError) {
val pos = if(lastStatement is Subroutine) subroutine.position else lastStatement.position
errors.err("subroutine '${subroutine.name}' has result value(s) but doesn't end with a return statement", pos)
}
}
}
@ -763,6 +794,9 @@ internal class AstChecker(private val program: Program,
// FLOATS enabled?
if(!compilerOptions.floats && (decl.datatype.isFloat || decl.datatype.isFloatArray) && decl.type != VarDeclType.MEMORY)
err("floating point used, but that is not enabled via options")
else if(compilerOptions.compTarget.name in arrayOf(PETTarget.NAME, C128Target.NAME) && decl.type != VarDeclType.CONST && (decl.datatype.isFloat || decl.datatype.isFloatArray)) {
err("pet32 and c128 target do not support floating point numbers yet")
}
// ARRAY without size specifier MUST have an iterable initializer value
if(decl.isArray && decl.arraysize==null) {
@ -1669,6 +1703,9 @@ internal class AstChecker(private val program: Program,
if(whenStmt.condition.constValue(program)!=null)
errors.warn("when-value is a constant and will always result in the same choice", whenStmt.condition.position)
if(whenStmt.betterAsOnGoto(program, compilerOptions))
errors.info("when statement can be replaced with on..goto", whenStmt.position)
super.visit(whenStmt)
}
@ -2063,6 +2100,12 @@ internal class AstChecker(private val program: Program,
return false
}
override fun visit(onGoto: OnGoto) {
if(!onGoto.index.inferType(program).getOrUndef().isUnsignedByte) {
errors.err("on..goto index must be an unsigned byte", onGoto.index.position)
}
}
}
internal fun checkUnusedReturnValues(call: FunctionCallStatement, target: Statement, errors: IErrorReporter) {

View File

@ -20,19 +20,6 @@ internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: Compila
checker.visit(this)
}
internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, errors: IErrorReporter) {
val fixer = BeforeAsmAstChanger(this, compilerOptions, errors)
fixer.visit(this)
while (errors.noErrors() && fixer.applyModifications() > 0) {
fixer.visit(this)
}
val cleaner = BeforeAsmTypecastCleaner(this, errors)
cleaner.visit(this)
while (errors.noErrors() && cleaner.applyModifications() > 0) {
cleaner.visit(this)
}
}
internal fun Program.reorderStatements(errors: IErrorReporter) {
val reorder = StatementReorderer(this, errors)
reorder.visit(this)
@ -103,8 +90,8 @@ internal fun Program.addTypecasts(errors: IErrorReporter, options: CompilationOp
caster.applyModifications()
}
fun Program.desugaring(errors: IErrorReporter) {
val desugar = CodeDesugarer(this, errors)
fun Program.desugaring(errors: IErrorReporter, options: CompilationOptions) {
val desugar = CodeDesugarer(this, options.compTarget, errors)
desugar.visit(this)
while(errors.noErrors() && desugar.applyModifications()>0)
desugar.visit(this)

View File

@ -139,7 +139,7 @@ class AstPreprocessor(val program: Program,
}
} else {
// handle declaration of a single variable
if(decl.value!=null && decl.datatype.isNumericOrBool) {
if(decl.value!=null && !decl.datatype.isIterable) {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, null, false, decl.position)
val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position)
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))

View File

@ -3,6 +3,7 @@ package prog8.compiler.astprocessing
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.defaultZero
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.*
@ -48,18 +49,27 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
val mods = mutableListOf<IAstModification>()
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
// and if an assembly block doesn't contain a rts/rti.
// and if an assembly block doesn't contain a rts/rti. AND if there's no return value(s) because we can't make one up!
if (!subroutine.isAsmSubroutine) {
if(subroutine.isEmpty()) {
val returnStmt = Return(arrayOf(), subroutine.position)
mods += IAstModification.InsertLast(returnStmt, subroutine)
if(subroutine.returntypes.isNotEmpty())
errors.err("subroutine is missing a return statement with value(s)", subroutine.position)
else {
val returnStmt = Return(arrayOf(), subroutine.position)
mods += IAstModification.InsertLast(returnStmt, subroutine)
}
} else {
val last = subroutine.statements.last()
if((last !is InlineAssembly || !last.hasReturnOrRts()) && last !is Return) {
val lastStatement = subroutine.statements.reversed().firstOrNull { it !is Subroutine }
if(lastStatement !is Return) {
val returnStmt = Return(arrayOf(), subroutine.position)
mods += IAstModification.InsertLast(returnStmt, subroutine)
if(subroutine.returntypes.isNotEmpty()) {
// .... we cannot return this as an error, because that also breaks legitimate cases where the return is done from within a nested scope somewhere
// errors.err("subroutine is missing a return statement with value(s)", subroutine.position)
} else {
val returnStmt = Return(arrayOf(), subroutine.position)
mods += IAstModification.InsertLast(returnStmt, subroutine)
}
}
}
}
@ -76,8 +86,20 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
&& prevStmt !is Subroutine
&& prevStmt !is Return
) {
val returnStmt = Return(arrayOf(), subroutine.position)
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
if(!subroutine.inline) {
if(outerScope is Subroutine && outerScope.returntypes.isNotEmpty()) {
if(outerScope.returntypes.size>1 || !outerScope.returntypes[0].isNumericOrBool) {
errors.err("subroutine is missing a return statement to avoid falling through into nested subroutine", outerStatements[subroutineStmtIdx-1].position)
} else {
val zero = defaultZero(outerScope.returntypes[0].base, Position.DUMMY)
val returnStmt = Return(arrayOf(zero), outerStatements[subroutineStmtIdx - 1].position)
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
}
} else {
val returnStmt = Return(arrayOf(), outerStatements[subroutineStmtIdx - 1].position)
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
}
}
}
}

View File

@ -5,13 +5,10 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.BaseDataType
import prog8.code.core.ComparisonOperators
import prog8.code.core.IErrorReporter
import prog8.code.core.Position
import prog8.code.core.*
internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
internal class CodeDesugarer(val program: Program, private val target: ICompilationTarget, private val errors: IErrorReporter) : AstWalker() {
// Some more code shuffling to simplify the Ast that the codegenerator has to process.
// Several changes have already been done by the StatementReorderer !
@ -27,6 +24,7 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep
// - @(&var) and @(&var+1) replaced by lsb(var) and msb(var) if var is a word
// - flatten chained assignments
// - remove alias nodes
// - convert on..goto/call to jumpaddr array and separate goto/call
override fun after(alias: Alias, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(alias, parent as IStatementContainer))
@ -106,7 +104,7 @@ if not CONDITION
untilLoop.body,
IfElse(invertCondition(untilLoop.condition, program),
AnonymousScope(mutableListOf(program.jumpLabel(loopLabel)), pos),
AnonymousScope(mutableListOf(), pos),
AnonymousScope.empty(),
pos)
), pos)
return listOf(IAstModification.ReplaceNode(untilLoop, replacement, parent))
@ -150,7 +148,7 @@ _after:
loopLabel,
IfElse(invertCondition(whileLoop.condition, program),
AnonymousScope(mutableListOf(program.jumpLabel(afterLabel)), pos),
AnonymousScope(mutableListOf(), pos),
AnonymousScope.empty(),
pos),
whileLoop.body,
program.jumpLabel(loopLabel),
@ -350,4 +348,60 @@ _after:
return noModifications
}
override fun after(ongoto: OnGoto, parent: Node): Iterable<IAstModification> {
val indexDt = ongoto.index.inferType(program).getOrUndef()
if(!indexDt.isUnsignedByte)
return noModifications
val numlabels = ongoto.labels.size
val split = if(ongoto.isCall)
true // for calls (indirect JSR), split array is always the optimal choice
else
target.cpu==CpuType.CPU6502 // for goto (indirect JMP), split array is optimal for 6502, but NOT for the 65C02 (it has a different JMP addressing mode available)
val arrayDt = DataType.arrayFor(BaseDataType.UWORD, split)
val labelArray = ArrayLiteral(InferredTypes.knownFor(arrayDt), ongoto.labels.toTypedArray(), ongoto.position)
val jumplistArray = VarDecl.createAutoOptionalSplit(labelArray)
val indexValue: Expression
val conditionVar: VarDecl?
val assignIndex: Assignment?
// put condition in temp var, if it is not simple; to avoid evaluating expression multiple times
if (ongoto.index.isSimple) {
indexValue = ongoto.index
assignIndex = null
conditionVar = null
} else {
conditionVar = VarDecl.createAuto(indexDt)
indexValue = IdentifierReference(listOf(conditionVar.name), conditionVar.position)
val varTarget = AssignTarget(indexValue, null, null, null, false, conditionVar.position)
assignIndex = Assignment(varTarget, ongoto.index, AssignmentOrigin.USERCODE, ongoto.position)
}
val callTarget = ArrayIndexedExpression(IdentifierReference(listOf(jumplistArray.name), jumplistArray.position), ArrayIndex(indexValue.copy(), indexValue.position), ongoto.position)
val callIndexed = AnonymousScope.empty(ongoto.position)
if(ongoto.isCall) {
callIndexed.statements.add(FunctionCallStatement(IdentifierReference(listOf("call"), ongoto.position), mutableListOf(callTarget), true, ongoto.position))
} else {
callIndexed.statements.add(Jump(callTarget, ongoto.position))
}
val ifSt = if(ongoto.elsepart==null || ongoto.elsepart!!.isEmpty()) {
// if index<numlabels call(labels[index])
val compare = BinaryExpression(indexValue.copy(), "<", NumericLiteral.optimalInteger(numlabels, ongoto.position), ongoto.position)
IfElse(compare, callIndexed, AnonymousScope.empty(), ongoto.position)
} else {
// if index>=numlabels elselabel() else call(labels[index])
val compare = BinaryExpression(indexValue.copy(), ">=", NumericLiteral.optimalInteger(numlabels, ongoto.position), ongoto.position)
IfElse(compare, ongoto.elsepart!!, callIndexed, ongoto.position)
}
val replacementScope = AnonymousScope(if(conditionVar==null)
mutableListOf(ifSt, jumplistArray)
else
mutableListOf(conditionVar, assignIndex!!, ifSt, jumplistArray)
, ongoto.position)
return listOf(IAstModification.ReplaceNode(ongoto, replacementScope, parent))
}
}

View File

@ -76,6 +76,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
is VarDecl -> transform(statement)
is When -> transform(statement)
is WhileLoop -> throw FatalAstException("while loops must have been converted to jumps")
is OnGoto -> throw FatalAstException("ongoto must have been converted to array and separate call/goto")
}
}
@ -772,7 +773,6 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
return cast
}
private fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
return if (SourceCode.isLibraryResource(filename)) {
return com.github.michaelbull.result.runCatching {

View File

@ -77,7 +77,7 @@ main {
}"""
val result = compileText(Cx16Target(), false, src, outputDir, writeAssembly = false)
val statements = result!!.compilerAst.entrypoint.statements
statements.size shouldBe 7
statements.size shouldBe 8
val a1 = statements[2] as Assignment
val a2 = statements[3] as Assignment
val a3 = statements[4] as Assignment

View File

@ -36,6 +36,7 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
warnSymbolShadowing = false,
quietAll = true,
quietAssembler = true,
showTimings = false,
asmListfile = false,
includeSourcelines = false,
experimentalCodegen = false,
@ -156,6 +157,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
"interpolation",
"kefrenbars",
"keyboardhandler",
"landscape",
"life",
"mandelbrot",
"multi-irq-old",

View File

@ -28,6 +28,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
warnSymbolShadowing = false,
quietAll = true,
quietAssembler = true,
showTimings = false,
asmListfile = false,
includeSourcelines = false,
experimentalCodegen = false,

View File

@ -262,12 +262,13 @@ class TestMemory: FunSpec({
shouldThrow<IllegalArgumentException> {
target.memorySize(BaseDataType.UNDEFINED)
}
shouldThrow<IllegalArgumentException> {
target.memorySize(BaseDataType.LONG)
shouldThrow<NoSuchElementException> {
target.memorySize(DataType.arrayFor(BaseDataType.LONG), 10)
}
target.memorySize(BaseDataType.BOOL) shouldBe 1
target.memorySize(BaseDataType.BYTE) shouldBe 1
target.memorySize(BaseDataType.WORD) shouldBe 2
target.memorySize(BaseDataType.LONG) shouldBe 4
target.memorySize(BaseDataType.FLOAT) shouldBe target.FLOAT_MEM_SIZE
target.memorySize(DataType.BOOL, null) shouldBe 1
@ -283,13 +284,14 @@ class TestMemory: FunSpec({
target.memorySize(DataType.arrayFor(BaseDataType.BOOL), 10) shouldBe 10
target.memorySize(DataType.arrayFor(BaseDataType.BYTE), 10) shouldBe 10
target.memorySize(DataType.arrayFor(BaseDataType.WORD), 10) shouldBe 20
target.memorySize(DataType.arrayFor(BaseDataType.WORD), 10) shouldBe 20
target.memorySize(DataType.arrayFor(BaseDataType.UWORD), 10) shouldBe 20
target.memorySize(DataType.arrayFor(BaseDataType.FLOAT), 10) shouldBe 10*target.FLOAT_MEM_SIZE
target.memorySize(DataType.arrayFor(BaseDataType.WORD, true), 10) shouldBe 20
target.memorySize(DataType.arrayFor(BaseDataType.UWORD, true), 10) shouldBe 20
target.memorySize(DataType.BOOL, 10) shouldBe 10
target.memorySize(DataType.UWORD, 10) shouldBe 20
target.memorySize(DataType.LONG, 10) shouldBe 40
target.memorySize(DataType.FLOAT, 10) shouldBe 10*target.FLOAT_MEM_SIZE
}
}

View File

@ -37,16 +37,16 @@ class TestNumericLiteral: FunSpec({
sameValueAndType(NumericLiteral(BaseDataType.UWORD, 12345.0, dummyPos), NumericLiteral(BaseDataType.UWORD, 12345.0, dummyPos)) shouldBe true
}
test("test truncating") {
test("test truncating avoidance") {
shouldThrow<ExpressionError> {
NumericLiteral(BaseDataType.BYTE, -2.345, dummyPos)
}.message shouldContain "refused truncating"
}.message shouldContain "float value given for integer"
shouldThrow<ExpressionError> {
NumericLiteral(BaseDataType.BYTE, -2.6, dummyPos)
}.message shouldContain "refused truncating"
}.message shouldContain "float value given for integer"
shouldThrow<ExpressionError> {
NumericLiteral(BaseDataType.UWORD, 2222.345, dummyPos)
}.message shouldContain "refused truncating"
}.message shouldContain "float value given for integer"
NumericLiteral(BaseDataType.UBYTE, 2.0, dummyPos).number shouldBe 2.0
NumericLiteral(BaseDataType.BYTE, -2.0, dummyPos).number shouldBe -2.0
NumericLiteral(BaseDataType.UWORD, 2222.0, dummyPos).number shouldBe 2222.0
@ -213,7 +213,7 @@ class TestNumericLiteral: FunSpec({
test("cast can change value") {
fun num(dt: BaseDataType, num: Double): NumericLiteral {
val n = NumericLiteral(dt, num, Position.DUMMY)
n.linkParents(AnonymousScope(mutableListOf(), Position.DUMMY))
n.linkParents(AnonymousScope.empty())
return n
}
val cast1 = num(BaseDataType.UBYTE, 200.0).cast(BaseDataType.BYTE, false)
@ -233,7 +233,7 @@ class TestNumericLiteral: FunSpec({
test("convert cannot change value") {
fun num(dt: BaseDataType, num: Double): NumericLiteral {
val n = NumericLiteral(dt, num, Position.DUMMY)
n.linkParents(AnonymousScope(mutableListOf(), Position.DUMMY))
n.linkParents(AnonymousScope.empty())
return n
}
num(BaseDataType.UBYTE, 200.0).convertTypeKeepValue(BaseDataType.BYTE).isValid shouldBe false

View File

@ -22,7 +22,9 @@ import prog8.code.core.Position
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8.vm.VmRunner
import prog8tests.helpers.*
import kotlin.io.path.readText
class TestOptimization: FunSpec({
@ -225,7 +227,7 @@ other {
}
"""
val result = compileText(C64Target(), optimize=false, src, outputDir, writeAssembly = false)!!
val assignFF = result.compilerAst.entrypoint.statements.last() as Assignment
val assignFF = result.compilerAst.entrypoint.statements.dropLast(1).last() as Assignment
assignFF.isAugmentable shouldBe true
assignFF.target.identifier!!.nameInSource shouldBe listOf("ff")
val value = assignFF.value as BinaryExpression
@ -252,7 +254,7 @@ other {
}
"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
result.compilerAst.entrypoint.statements.size shouldBe 7
result.compilerAst.entrypoint.statements.size shouldBe 8
val alldecls = result.compilerAst.entrypoint.allDefinedSymbols.toList()
alldecls.map { it.first } shouldBe listOf("unused_but_shared", "usedvar_only_written", "usedvar")
}
@ -276,12 +278,12 @@ other {
}
}"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
result.compilerAst.entrypoint.statements.size shouldBe 3
result.compilerAst.entrypoint.statements.size shouldBe 4
val ifstmt = result.compilerAst.entrypoint.statements[0] as IfElse
ifstmt.truepart.statements.size shouldBe 1
(ifstmt.truepart.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
val func2 = result.compilerAst.entrypoint.statements[2] as Subroutine
func2.statements.size shouldBe 2
val func2 = result.compilerAst.entrypoint.statements.last() as Subroutine
func2.statements.size shouldBe 3
(func2.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
}
@ -312,7 +314,7 @@ main {
}
}"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
result.compilerAst.entrypoint.statements.size shouldBe 0
result.compilerAst.entrypoint.statements.size shouldBe 1
result.compilerAst.entrypoint.definingScope.statements.size shouldBe 1
}
@ -350,7 +352,7 @@ main {
z6 = z1 - 5
*/
val statements = result.compilerAst.entrypoint.statements
statements.size shouldBe 12
statements.size shouldBe 13
val z1decl = statements[0] as VarDecl
val z1init = statements[1] as Assignment
val z2decl = statements[2] as VarDecl
@ -395,8 +397,8 @@ main {
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 5
val assign=stmts.last() as Assignment
stmts.size shouldBe 6
val assign=stmts[4] as Assignment
(assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
}
@ -412,8 +414,8 @@ main {
"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 5
val assign=stmts.last() as Assignment
stmts.size shouldBe 6
val assign=stmts[4] as Assignment
(assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
}
@ -431,7 +433,7 @@ main {
"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 10
stmts.size shouldBe 11
stmts.filterIsInstance<VarDecl>().size shouldBe 5
stmts.filterIsInstance<Assignment>().size shouldBe 5
}
@ -508,7 +510,7 @@ main {
xx += 6
*/
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 6
stmts.size shouldBe 7
stmts.filterIsInstance<VarDecl>().size shouldBe 3
stmts.filterIsInstance<Assignment>().size shouldBe 3
}
@ -537,13 +539,13 @@ main {
xx += 10
*/
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 7
stmts.size shouldBe 8
stmts.filterIsInstance<VarDecl>().size shouldBe 2
stmts.filterIsInstance<Assignment>().size shouldBe 5
val assignXX1 = stmts[1] as Assignment
assignXX1.target.identifier!!.nameInSource shouldBe listOf("xx")
assignXX1.value shouldBe NumericLiteral(BaseDataType.UWORD, 20.0, Position.DUMMY)
val assignXX2 = stmts.last() as Assignment
val assignXX2 = stmts[6] as Assignment
assignXX2.target.identifier!!.nameInSource shouldBe listOf("xx")
val xxValue = assignXX2.value as BinaryExpression
xxValue.operator shouldBe "+"
@ -577,7 +579,7 @@ main {
thingy++
*/
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 6
stmts.size shouldBe 7
val ifStmt = stmts[5] as IfElse
val containment = ifStmt.condition as ContainmentCheck
(containment.element as IdentifierReference).nameInSource shouldBe listOf("source")
@ -612,7 +614,7 @@ main {
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 5
stmts.size shouldBe 6
val ifStmt = stmts[4] as IfElse
val containment = ifStmt.condition as ContainmentCheck
(containment.element as IdentifierReference).nameInSource shouldBe listOf("source")
@ -634,7 +636,7 @@ main {
}"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 5
stmts.size shouldBe 6
val ifStmt = stmts[4] as IfElse
ifStmt.condition shouldBe instanceOf<BinaryExpression>()
}
@ -652,7 +654,7 @@ main {
}"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 5
stmts.size shouldBe 6
val ifStmt = stmts[4] as IfElse
ifStmt.condition shouldBe instanceOf<BinaryExpression>()
}
@ -670,7 +672,7 @@ main {
}"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 5
stmts.size shouldBe 6
val ifStmt = stmts[4] as IfElse
ifStmt.condition shouldBe instanceOf<BinaryExpression>()
}
@ -687,7 +689,7 @@ main {
}"""
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 3
stmts.size shouldBe 4
}
test("repeated assignments to IO register should remain") {
@ -828,7 +830,7 @@ main {
val errors = ErrorReporterForTests()
val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = false, errors = errors)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 4
st.size shouldBe 5
val xxConst = st[0] as VarDecl
xxConst.type shouldBe VarDeclType.CONST
xxConst.name shouldBe "xx"
@ -872,7 +874,7 @@ main {
}"""
val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 8
st.size shouldBe 9
val if1c = (st[4] as IfElse).condition as PrefixExpression
val if2c = (st[5] as IfElse).condition as PrefixExpression
val if3c = (st[6] as IfElse).condition as PrefixExpression
@ -915,7 +917,7 @@ main {
}"""
val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 12
st.size shouldBe 13
val if1 = st[4] as IfElse
val if2 = st[5] as IfElse
val if3 = st[6] as IfElse
@ -970,7 +972,7 @@ main {
val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 17
st.size shouldBe 18
val answerValue = (st[3] as Assignment).value
answerValue shouldBe NumericLiteral(BaseDataType.UWORD, 0.0, Position.DUMMY)
@ -1019,7 +1021,7 @@ main {
}"""
val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 3
st.size shouldBe 4
val ifCond1 = (st[0] as IfElse).condition as BinaryExpression
val ifCond2 = (st[1] as IfElse).condition as BinaryExpression
val ifCond3 = (st[2] as IfElse).condition as BinaryExpression
@ -1178,4 +1180,54 @@ main {
}"""
compileText(C64Target(), false, src, outputDir, writeAssembly = true) shouldNotBe null
}
test("correct unused block removal for virtual target") {
val src="""
main {
sub start() {
cx16.r0++
}
}
some_block {
uword buffer = memory("arena", 2000, 0)
}
other_block {
sub redherring (uword buffer) {
%ir {{
loadm.w r99000,other_block.redherring.buffer
}}
}
}
"""
val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
VmRunner().runProgram(virtfile.readText(), false)
}
test("correct unused block removal for c64 target") {
val src="""
main {
sub start() {
cx16.r0++
}
}
some_block {
uword buffer = memory("arena", 2000, 0)
}
other_block {
sub redherring (uword buffer) {
%asm {{
lda #<p8b_other_block.p8s_redherring.p8v_buffer
ldy #>p8b_other_block.p8s_redherring.p8v_buffer
}}
}
}"""
compileText(C64Target(), true, src, outputDir) shouldNotBe null
}
})

View File

@ -19,11 +19,7 @@ import prog8.code.core.Position
import prog8.code.core.unescape
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.encodings.Encoder
import prog8.code.target.encodings.AtasciiEncoding
import prog8.code.target.encodings.C64osEncoding
import prog8.code.target.encodings.IsoEncoding
import prog8.code.target.encodings.PetsciiEncoding
import prog8.code.target.encodings.*
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
import java.io.CharConversionException
@ -227,7 +223,7 @@ class TestStringEncodings: FunSpec({
context("iso") {
test("iso accepts iso-characters") {
val result = IsoEncoding.encode("a_~ëç")
val result = IsoEncoding.encode("a_~ëç", false)
result.getOrElse { throw it }.map {it.toInt()} shouldBe listOf(97, 95, 126, 235, 231)
}
@ -276,13 +272,13 @@ class TestStringEncodings: FunSpec({
passthrough[1] shouldBe '\u801b'
passthrough[2] shouldBe '\u8099'
passthrough[3] shouldBe '\u80ff'
var encoded = Encoder.encodeString(passthrough, Encoding.PETSCII)
var encoded = Encoder(false).encodeString(passthrough, Encoding.PETSCII)
encoded shouldBe listOf<UByte>(0u, 0x1bu, 0x99u, 0xffu)
encoded = Encoder.encodeString(passthrough, Encoding.ATASCII)
encoded = Encoder(false).encodeString(passthrough, Encoding.ATASCII)
encoded shouldBe listOf<UByte>(0u, 0x1bu, 0x99u, 0xffu)
encoded = Encoder.encodeString(passthrough, Encoding.SCREENCODES)
encoded = Encoder(false).encodeString(passthrough, Encoding.SCREENCODES)
encoded shouldBe listOf<UByte>(0u, 0x1bu, 0x99u, 0xffu)
encoded = Encoder.encodeString(passthrough, Encoding.ISO)
encoded = Encoder(false).encodeString(passthrough, Encoding.ISO)
encoded shouldBe listOf<UByte>(0u, 0x1bu, 0x99u, 0xffu)
}
@ -413,5 +409,39 @@ class TestStringEncodings: FunSpec({
val char2 = (main.statements[5] as Assignment).value as NumericLiteral
char2.number shouldBe 80.0
}
test("with newline conversion") {
val encoder = Encoder(true)
encoder.encodeString("\n\r", Encoding.PETSCII) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.SCREENCODES) shouldBe listOf<UByte>(141u, 141u)
encoder.encodeString("\n\r", Encoding.ATASCII) shouldBe listOf<UByte>(155u, 155u)
encoder.encodeString("\n\r", Encoding.ISO) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.ISO5) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.ISO16) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.CP437) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.KATAKANA) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.C64OS) shouldBe listOf<UByte>(13u, 13u)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.PETSCII).map { it.code } shouldBe listOf(10, 10)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.ISO).map { it.code } shouldBe listOf(10, 10)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.CP437).map { it.code } shouldBe listOf(10, 13)
}
test("without newline conversion") {
val encoder = Encoder(false)
encoder.encodeString("\n\r", Encoding.PETSCII) shouldBe listOf<UByte>(13u, 13u)
encoder.encodeString("\n\r", Encoding.SCREENCODES) shouldBe listOf<UByte>(141u, 141u)
encoder.encodeString("\n\r", Encoding.ATASCII) shouldBe listOf<UByte>(155u, 155u)
encoder.encodeString("\n\r", Encoding.ISO) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.ISO5) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.ISO16) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.CP437) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.KATAKANA) shouldBe listOf<UByte>(10u, 13u)
encoder.encodeString("\n\r", Encoding.C64OS) shouldBe listOf<UByte>(13u, 13u)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.PETSCII).map { it.code } shouldBe listOf(10, 10)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.ISO).map { it.code } shouldBe listOf(10, 13)
encoder.decodeString(listOf<UByte>(10u, 13u), Encoding.CP437).map { it.code } shouldBe listOf(10, 13)
}
})

View File

@ -54,6 +54,9 @@ class TestSubroutines: FunSpec({
}
asmsub asmfunc(str thing @AY) {
%asm {{
rts
}}
}
sub func(str thing) {
@ -67,12 +70,12 @@ class TestSubroutines: FunSpec({
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
asmfunc.isAsmSubroutine shouldBe true
asmfunc.statements.isEmpty() shouldBe true
asmfunc.statements.size shouldBe 1
func.isAsmSubroutine shouldBe false
withClue("str param for subroutines should be changed into UWORD") {
asmfunc.parameters.single().type shouldBe DataType.UWORD
func.parameters.single().type shouldBe DataType.UWORD
func.statements.size shouldBe 4
func.statements.size shouldBe 5
val paramvar = func.statements[0] as VarDecl
paramvar.name shouldBe "thing"
paramvar.datatype shouldBe DataType.UWORD
@ -174,6 +177,9 @@ class TestSubroutines: FunSpec({
}
asmsub asmfunc(ubyte[] thing @AY) {
%asm {{
rts
}}
}
sub func(ubyte[] thing) {

View File

@ -54,7 +54,7 @@ class TestTypecasts: FunSpec({
}"""
val result = compileText(C64Target(), false, text, outputDir, writeAssembly = false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 6
stmts.size shouldBe 7
val expr = (stmts[5] as Assignment).value as BinaryExpression
expr.operator shouldBe "and"
(expr.left as IdentifierReference).nameInSource shouldBe listOf("bb2") // no cast
@ -157,7 +157,7 @@ main {
}"""
val result = compileText(C64Target(), false, text, outputDir, writeAssembly = false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 7
stmts.size shouldBe 8
val fcall1 = ((stmts[4] as Assignment).value as IFunctionCall)
fcall1.args[0] shouldBe NumericLiteral(BaseDataType.BOOL, 1.0, Position.DUMMY)
fcall1.args[1] shouldBe NumericLiteral(BaseDataType.BOOL, 0.0, Position.DUMMY)
@ -209,7 +209,7 @@ main {
}"""
val result = compileText(C64Target(), false, text, outputDir, writeAssembly = false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 3
stmts.size shouldBe 4
}
test("ubyte to word casts") {
@ -224,7 +224,7 @@ main {
val result = compileText(C64Target(), true, src, outputDir, writeAssembly = false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 4
stmts.size shouldBe 5
val assign1tc = (stmts[2] as Assignment).value as TypecastExpression
val assign2tc = (stmts[3] as Assignment).value as TypecastExpression
assign1tc.type shouldBe BaseDataType.WORD
@ -255,7 +255,7 @@ main {
}"""
val result = compileText(C64Target(), false, text, outputDir, writeAssembly = false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 8
stmts.size shouldBe 9
val arg1 = (stmts[2] as IFunctionCall).args.single()
val arg2 = (stmts[3] as IFunctionCall).args.single()
val arg3 = (stmts[4] as IFunctionCall).args.single()
@ -318,23 +318,21 @@ main {
errors.errors[1] shouldContain "no cast"
}
test("refuse to truncate float literal 1") {
test("allow explicit float literal cast to integer") {
val text = """
%option enable_floats
main {
sub start() {
float @shared fl = 3.456 as uword
fl = 1.234 as uword
cx16.r0 = 1234.5678 as uword
cx16.r1L = 99.333 as ubyte
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, text, outputDir, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "refused"
errors.errors[1] shouldContain "refused"
compileText(C64Target(), false, text, outputDir, errors=errors) shouldNotBe null
errors.errors.size shouldBe 0
}
test("refuse to truncate float literal 2") {
test("refuse to truncate float inplace") {
val text = """
%option enable_floats
main {
@ -350,23 +348,6 @@ main {
errors.errors[0] shouldContain "in-place makes no sense"
}
test("refuse to truncate float literal 3") {
val text = """
%option enable_floats
main {
sub start() {
uword @shared ww = 3.456 as uword
ww++
ww = 3.456 as uword
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, text, outputDir, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "refused"
errors.errors[1] shouldContain "refused"
}
test("correct implicit casts of signed number comparison and logical expressions") {
val text = """
%import floats
@ -903,7 +884,7 @@ main {
val result = compileText(C64Target(), false, src, outputDir, writeAssembly = false)!!
val program = result.compilerAst
val st = program.entrypoint.statements
st.size shouldBe 1
st.size shouldBe 2
val assign = st[0] as Assignment
assign.target.inferType(program).getOrUndef().base shouldBe BaseDataType.BYTE
val ifexpr = assign.value as IfExpression
@ -928,7 +909,7 @@ main {
val result = compileText(C64Target(), false, src, outputDir, writeAssembly = false)!!
val program = result.compilerAst
val st = program.entrypoint.statements
st.size shouldBe 6
st.size shouldBe 7
val v1 = (st[2] as Assignment).value as BinaryExpression
v1.operator shouldBe "+"
(v1.left as IdentifierReference).nameInSource shouldBe listOf("cx16","r0")

View File

@ -52,7 +52,7 @@ class TestConst: FunSpec({
// cx16.r5s = llw - 1899
// cx16.r7s = llw + 99
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 9
stmts.size shouldBe 10
val addR0value = (stmts[4] as Assignment).value
val binexpr0 = addR0value as BinaryExpression
@ -109,7 +109,7 @@ class TestConst: FunSpec({
// result++
// result = llw * 18.0
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 12
stmts.size shouldBe 13
val mulR0Value = (stmts[3] as Assignment).value
val binexpr0 = mulR0Value as BinaryExpression
@ -157,7 +157,7 @@ class TestConst: FunSpec({
// cx16.r3s = llw /2 *10
// cx16.r4s = llw *90 /5
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 7
stmts.size shouldBe 8
val mulR0Value = (stmts[2] as Assignment).value
val binexpr0 = mulR0Value as BinaryExpression
@ -251,7 +251,7 @@ main {
}"""
val result = compileText(Cx16Target(), true, src, outputDir, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 5
st.size shouldBe 6
(st[0] as VarDecl).type shouldBe VarDeclType.CONST
val assignv1 = (st[2] as Assignment).value
val assignv2 = (st[4] as Assignment).value
@ -274,7 +274,7 @@ main {
}"""
val result = compileText(Cx16Target(), false, src, outputDir, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 6
st.size shouldBe 7
((st[0] as VarDecl).value as NumericLiteral).number shouldBe 0x2000
((st[1] as VarDecl).value as NumericLiteral).number shouldBe 0x9e00
((st[2] as VarDecl).value as NumericLiteral).number shouldBe 0x9e00+2*30

View File

@ -288,13 +288,13 @@ class TestProg8Parser: FunSpec( {
}
"""
val module = parseModule(SourceCode.Text(srcText))
assertPositionOf(module, Regex("^string:[0-9a-f\\-]+$"), 1, 0)
assertPositionOf(module, Regex("^string:[0-9a-f\\-]+$"), 1, 1)
}
test("of Module parsed from a file") {
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
val module = parseModule(ImportFileSystem.getFile(path))
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 1)
}
test("of non-root Nodes parsed from file") {
@ -302,7 +302,7 @@ class TestProg8Parser: FunSpec( {
val module = parseModule(ImportFileSystem.getFile(path))
val mpf = module.position.file
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 1)
val mainBlock = module.statements.filterIsInstance<Block>()[0]
assertPositionOf(mainBlock, mpf, 2, 1, 4)
val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0]
@ -907,7 +907,7 @@ class TestProg8Parser: FunSpec( {
"""
val result = compileText(C64Target(), false, text, outputDir, writeAssembly = false)!!
val start = result.compilerAst.entrypoint
val containmentChecks = start.statements.takeLast(4)
val containmentChecks = start.statements.takeLast(5)
(containmentChecks[0] as IfElse).condition shouldBe instanceOf<ContainmentCheck>()
(containmentChecks[1] as IfElse).condition shouldBe instanceOf<ContainmentCheck>()
(containmentChecks[2] as Assignment).value shouldBe instanceOf<ContainmentCheck>()
@ -948,7 +948,7 @@ class TestProg8Parser: FunSpec( {
"""
val result = compileText(C64Target(), false, text, outputDir, writeAssembly = false)!!
val stmt = result.compilerAst.entrypoint.statements
stmt.size shouldBe 12
stmt.size shouldBe 13
val var1 = stmt[0] as VarDecl
var1.sharedWithAsm shouldBe true
var1.zeropage shouldBe ZeropageWish.REQUIRE_ZEROPAGE
@ -998,7 +998,7 @@ main {
; curly braces without newline
sub start () { foo() derp() other() }
sub foo() { cx16.r0++ }
asmsub derp() { %asm {{ nop }} %ir {{ load.b r0,1 }} }
asmsub derp() { %asm {{ nop rts }} %ir {{ load.b r0,1 return }} }
; curly braces on next line
sub other()
@ -1014,6 +1014,7 @@ main {
{{
txa
tay
rts
}}
}
@ -1022,6 +1023,7 @@ main {
%ir
{{
load.b r0,1
return
}}
}
}"""
@ -1042,7 +1044,7 @@ main {
}"""
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 8
st.size shouldBe 9
val assigns = st.filterIsInstance<Assignment>()
(assigns[0].value as NumericLiteral).number shouldBe 12345
(assigns[1].value as NumericLiteral).number shouldBe 0xffee
@ -1053,11 +1055,54 @@ main {
test("oneliner") {
val src="""
main { sub start() { cx16.r0++ cx16.r1++ } }
other { asmsub thing() { %asm {{ inx }} } }
other { asmsub thing() { %asm {{ inx rts }} } }
"""
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 2
st.size shouldBe 3
}
test("allow type name as argument for sizeof()") {
val src="""
%option enable_floats
main {
sub start() {
bool @shared b
float @shared f
word @shared w
const long ll = 9999999
ubyte @shared ub1, ub2
ub1 = sys.SIZEOF_BOOL
ub2 = sys.SIZEOF_WORD
ub1 = sys.SIZEOF_LONG
ub2 = sys.SIZEOF_FLOAT
ub1 = sizeof(true)
ub2 = sizeof(1234)
ub1 = sizeof(12345678)
ub2 = sizeof(9.999)
ub1 = sizeof(b)
ub2 = sizeof(w)
ub1 = sizeof(ll)
ub2 = sizeof(f)
ub1 = sizeof(bool)
ub2 = sizeof(word)
ub1 = sizeof(long)
ub2 = sizeof(float)
}
}"""
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 27
val assignments = st.dropLast(1).takeLast(16)
assignments.forEach { a ->
(a as Assignment).value shouldBe instanceOf<NumericLiteral>()
}
}
})

View File

@ -243,4 +243,40 @@ main {
errors.errors.size shouldBe 1
errors.errors[0] shouldContain ":8:5: address must be a constant"
}
test("detect missing return") {
val src="""
main {
sub start() {
void read_loadlist()
void test2()
}
sub read_loadlist() -> bool {
cx16.r0++
if cx16.r0==0
return false
cx16.r1++
; TODO missing return! ERROR!
}
sub test2() -> bool {
cx16.r0++
return false
sub sub1() {
cx16.r0++
}
sub sub2() {
cx16.r0++
}
}
}"""
val errors = ErrorReporterForTests()
compileText(Cx16Target(), false, src, outputDir, errors, false) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "doesn't end with a return"
}
})

View File

@ -17,9 +17,7 @@ import prog8.code.ast.*
import prog8.code.core.BaseDataType
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8.code.target.*
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
@ -271,28 +269,6 @@ main {
errors.errors[0] shouldContain "has result value"
errors.errors[1] shouldContain "has result value"
}
test("missing return value is not a syntax error if there's an external goto") {
val src="""
main {
sub start() {
cx16.r0 = runit1()
runit2()
}
sub runit1() -> uword {
repeat {
cx16.r0++
goto runit2
}
}
sub runit2() {
cx16.r0++
}
}"""
compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null
}
}
context("variable declarations") {
@ -355,7 +331,7 @@ main {
}"""
val result = compileText(Cx16Target(), optimize=true, src, outputDir, writeAssembly=false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 12
st.size shouldBe 13
st[0] shouldBe instanceOf<VarDecl>() // x
st[2] shouldBe instanceOf<VarDecl>() // y
st[4] shouldBe instanceOf<VarDecl>() // z
@ -429,7 +405,7 @@ main {
errors.warnings.all { "dirty variable" in it } shouldBe true
val start = result.compilerAst.entrypoint
val st = start.statements
st.size shouldBe 9
st.size shouldBe 10
val assignments = st.filterIsInstance<Assignment>()
assignments.size shouldBe 2
assignments[0].target.identifier?.nameInSource shouldBe listOf("locwi")
@ -523,7 +499,7 @@ main {
}"""
val result = compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 7
stmts.size shouldBe 8
val assign1expr = (stmts[3] as Assignment).value as BinaryExpression
val assign2expr = (stmts[5] as Assignment).value as BinaryExpression
assign1expr.operator shouldBe "<<"
@ -554,7 +530,7 @@ main {
}"""
val result = compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 9
stmts.size shouldBe 10
}
test("alternative notation for negative containment check") {
@ -570,7 +546,7 @@ main {
"""
val result = compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 4
stmts.size shouldBe 5
val value1 = (stmts[2] as Assignment).value as PrefixExpression
val value2 = (stmts[3] as Assignment).value as PrefixExpression
value1.operator shouldBe "not"
@ -764,7 +740,7 @@ main {
}"""
val result=compileText(VMTarget(), optimize=true, src, outputDir, writeAssembly=false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 11
st.size shouldBe 12
val ifCond = (st[8] as IfElse).condition as BinaryExpression
ifCond.operator shouldBe ">="
@ -792,7 +768,7 @@ main {
val result=compileText(Cx16Target(), optimize=false, src, outputDir, writeAssembly=false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 6
st.size shouldBe 7
val value = (st[5] as Assignment).value as BinaryExpression
value.operator shouldBe "%"
}
@ -838,10 +814,9 @@ main {
}"""
val result = compileText(VMTarget(), optimize=true, src, outputDir, writeAssembly=false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 8
val assignUbbVal = ((st[5] as Assignment).value as TypecastExpression)
assignUbbVal.type shouldBe BaseDataType.UBYTE
assignUbbVal.expression shouldBe instanceOf<IdentifierReference>()
st.size shouldBe 9
val assignUbbVal = (st[5] as Assignment).value as IdentifierReference
assignUbbVal.inferType(result.compilerAst) shouldBe InferredTypes.knownFor(BaseDataType.BYTE)
val assignVaddr = (st[7] as Assignment).value as FunctionCallExpression
assignVaddr.target.nameInSource shouldBe listOf("mkword")
val tc = assignVaddr.args[0] as TypecastExpression
@ -970,6 +945,7 @@ main {
if cx16.r0==0
return cx16.r0+cx16.r1
defer cx16.r2++
return 999
}
}"""
val result = compileText(Cx16Target(), optimize=true, src, outputDir, writeAssembly=true)!!
@ -1078,6 +1054,80 @@ main {
compileText(Cx16Target(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null
compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null
}
test("using the cx16 virtual registers as various datatypes") {
val src="""
main {
sub start() {
uword uw = 9999
word sw = -2222
ubyte ub = 42
byte sb = -99
bool bb = true
cx16.r0 = uw
cx16.r0s = sw
cx16.r0L = ub
cx16.r0H = ub
cx16.r0sL = sb
cx16.r0sH = sb
cx16.r0bL = bb
cx16.r0bH = bb
uw = cx16.r0
sw = cx16.r0s
ub = cx16.r0L
ub = cx16.r0H
sb = cx16.r0sL
sb = cx16.r0sH
bb = cx16.r0bL
bb = cx16.r0bH
}
}"""
compileText(Cx16Target(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null
compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null
compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null
compileText(PETTarget(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null
compileText(C128Target(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null
}
test("on..goto") {
val src="""
main {
sub start() {
cx16.r13L = 1
cx16.r12L = 0
on cx16.r12L+1 call (
thing.func1,
thing.func2,
thing.func3)
else {
; not jumped
cx16.r0++
}
on cx16.r13L+1 goto (thing.func1, thing.func2, thing.func3)
}
}
thing {
sub func1() {
cx16.r10 += 1
}
sub func2() {
cx16.r10 += 2
}
sub func3() {
cx16.r10 += 3
}
}"""
compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null
compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null
compileText(Cx16Target(), optimize=false, src, outputDir, writeAssembly=true) shouldNotBe null
}
}
})

View File

@ -206,7 +206,7 @@ main {
}"""
val result = compileText(C64Target(), false, src, outputDir, writeAssembly = false)!!.compilerAst
val st = result.entrypoint.statements
st.size shouldBe 8
st.size shouldBe 9
st[0] shouldBe instanceOf<VarDecl>()
st[1] shouldBe instanceOf<VarDecl>()
st[2] shouldBe instanceOf<VarDecl>()

View File

@ -27,6 +27,7 @@ internal fun compileFile(
warnSymbolShadowing = false,
quietAll = true,
quietAssembler = true,
showTimings = false,
asmListfile = false,
includeSourcelines = false,
experimentalCodegen = false,

View File

@ -38,8 +38,7 @@ main {
zz = words[3]
}
}"""
val target = VMTarget()
val result = compileText(target, false, src, outputDir, writeAssembly = true)!!
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
VmRunner().runProgram(virtfile.readText(), false)
}
@ -58,8 +57,7 @@ main {
zz = words[3]
}
}"""
val target = VMTarget()
val result = compileText(target, false, src, outputDir, writeAssembly = true)!!
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
VmRunner().runProgram(virtfile.readText(), false)
}
@ -107,7 +105,7 @@ test {
}
}"""
val target = VMTarget()
var result = compileText(target, false, src, outputDir, writeAssembly = true)!!
var result = compileText(VMTarget(), false, src, outputDir, writeAssembly = true)!!
var virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
VmRunner().runProgram(virtfile.readText(), false)

View File

@ -520,4 +520,20 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
override fun visit(alias: Alias) {
output("alias ${alias.alias} = ${alias.target.nameInSource.joinToString(".")}")
}
override fun visit(onGoto: OnGoto) {
output(if(onGoto.isCall) "selectcall " else "selectgoto ")
onGoto.index.accept(this)
output(" from (")
onGoto.labels.forEachIndexed { idx, label ->
label.accept(this)
if(idx!=onGoto.labels.lastIndex)
output(", ")
}
outputln(")")
if(onGoto.elsepart!=null && onGoto.elsepart.isNotEmpty()) {
output(" else ")
onGoto.elsepart.accept(this)
}
}
}

View File

@ -7,7 +7,9 @@ import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import prog8.code.core.BaseDataType
import prog8.code.core.Encoding
import prog8.code.core.Position
import prog8.code.source.SourceCode
@ -135,6 +137,26 @@ interface IStatementContainer {
return null
}
fun hasReturnStatement(): Boolean {
fun hasReturnStatement(stmt: Statement): Boolean {
when(stmt) {
is AnonymousScope -> return stmt.statements.any { hasReturnStatement(it) }
is ForLoop -> return stmt.body.hasReturnStatement()
is IfElse -> return stmt.truepart.hasReturnStatement() || stmt.elsepart.hasReturnStatement()
is WhileLoop -> return stmt.body.hasReturnStatement()
is RepeatLoop -> return stmt.body.hasReturnStatement()
is UntilLoop -> return stmt.body.hasReturnStatement()
is When -> return stmt.choices.any { it.statements.hasReturnStatement() }
is ConditionalBranch -> return stmt.truepart.hasReturnStatement() || stmt.elsepart.hasReturnStatement()
is UnrollLoop -> return stmt.body.hasReturnStatement()
is Return -> return true
else -> return false
}
}
return statements.any { hasReturnStatement(it) }
}
val allDefinedSymbols: Sequence<Pair<String, Statement>>
get() {
return statements.asSequence().filterIsInstance<INamedStatement>().map { Pair(it.name, it as Statement) }

View File

@ -1,813 +0,0 @@
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.FatalAstException
import prog8.ast.SyntaxError
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.code.core.*
import prog8.code.source.SourceCode
import prog8.parser.Prog8ANTLRParser.*
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
/***************** Antlr Extension methods to create AST ****************/
private data class NumericLiteralNode(val number: Double, val datatype: BaseDataType)
private fun ParserRuleContext.toPosition() : Position {
val pathString = start.inputStream.sourceName
val filename = if(SourceCode.isRegularFilesystemPath(pathString)) {
val path = Path(pathString)
if(path.isRegularFile()) {
SourceCode.relative(path).toString()
} else {
path.toString()
}
} else {
pathString
}
// note: beware of TAB characters in the source text, they count as 1 column...
return Position(filename, start.line, start.charPositionInLine+1, start.charPositionInLine + 1 + start.stopIndex - start.startIndex)
}
internal fun BlockContext.toAst(isInLibrary: Boolean) : Block {
val blockstatements = block_statement().map {
when {
it.variabledeclaration()!=null -> it.variabledeclaration().toAst()
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst()
it.directive()!=null -> it.directive().toAst()
it.inlineasm()!=null -> it.inlineasm().toAst()
it.inlineir()!=null -> it.inlineir().toAst()
it.labeldef()!=null -> it.labeldef().toAst()
it.alias()!=null -> it.alias().toAst()
else -> throw FatalAstException("weird block node $it")
}
}
return Block(identifier().text, integerliteral()?.toAst()?.number?.toUInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
}
private fun Statement_blockContext.toAst(): MutableList<Statement> =
statement().asSequence().map { it.toAst() }.toMutableList()
private fun VariabledeclarationContext.toAst() : Statement {
vardecl()?.let {
return it.toAst(VarDeclType.VAR, null)
}
varinitializer()?.let {
return it.vardecl().toAst(VarDeclType.VAR, it.expression().toAst())
}
constdecl()?.let {
val cvarinit = it.varinitializer()
return cvarinit.vardecl().toAst(VarDeclType.CONST, cvarinit.expression().toAst())
}
memoryvardecl()?.let {
val mvarinit = it.varinitializer()
return mvarinit.vardecl().toAst(VarDeclType.MEMORY, mvarinit.expression().toAst())
}
throw FatalAstException("weird variable decl $this")
}
private fun SubroutinedeclarationContext.toAst() : Subroutine {
return when {
subroutine()!=null -> subroutine().toAst()
asmsubroutine()!=null -> asmsubroutine().toAst()
extsubroutine()!=null -> extsubroutine().toAst()
else -> throw FatalAstException("weird subroutine decl $this")
}
}
private fun StatementContext.toAst() : Statement {
val vardecl = variabledeclaration()?.toAst()
if(vardecl!=null) return vardecl
val assignment = assignment()?.toAst()
if(assignment!=null) return assignment
val augassign = augassignment()?.toAst()
if(augassign!=null) return augassign
postincrdecr()?.let {
val tgt = it.assign_target().toAst()
val operator = it.operator.text
val pos = it.toPosition()
// print("\u001b[92mINFO\u001B[0m ") // bright green
// println("${pos}: ++ and -- will be removed in a future version, please use +=1 or -=1 instead.") // .... if we decode to remove them one day
val addSubOne = BinaryExpression(tgt.toExpression(), if(operator=="++") "+" else "-", NumericLiteral.optimalInteger(1, pos), pos)
return Assignment(tgt, addSubOne, AssignmentOrigin.USERCODE, pos)
}
val directive = directive()?.toAst()
if(directive!=null) return directive
val label = labeldef()?.toAst()
if(label!=null) return label
val jump = unconditionaljump()?.toAst()
if(jump!=null) return jump
val fcall = functioncall_stmt()?.toAst()
if(fcall!=null) return fcall
val ifstmt = if_stmt()?.toAst()
if(ifstmt!=null) return ifstmt
val returnstmt = returnstmt()?.toAst()
if(returnstmt!=null) return returnstmt
val subroutine = subroutinedeclaration()?.toAst()
if(subroutine!=null) return subroutine
val asm = inlineasm()?.toAst()
if(asm!=null) return asm
val ir = inlineir()?.toAst()
if(ir!=null) return ir
val branchstmt = branch_stmt()?.toAst()
if(branchstmt!=null) return branchstmt
val forloop = forloop()?.toAst()
if(forloop!=null) return forloop
val untilloop = untilloop()?.toAst()
if(untilloop!=null) return untilloop
val whileloop = whileloop()?.toAst()
if(whileloop!=null) return whileloop
val repeatloop = repeatloop()?.toAst()
if(repeatloop!=null) return repeatloop
val whenstmt = whenstmt()?.toAst()
if(whenstmt!=null) return whenstmt
val breakstmt = breakstmt()?.toAst()
if(breakstmt!=null) return breakstmt
val continuestmt = continuestmt()?.toAst()
if(continuestmt!=null) return continuestmt
val unrollstmt = unrollloop()?.toAst()
if(unrollstmt!=null) return unrollstmt
val deferstmt = defer()?.toAst()
if(deferstmt!=null) return deferstmt
val aliasstmt = alias()?.toAst()
if(aliasstmt!=null) return aliasstmt
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
}
private fun AsmsubroutineContext.toAst(): Subroutine {
val inline = this.inline()!=null
val subdecl = asmsub_decl().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf()
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes.toMutableList(),
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, null, true, inline, statements = statements, position = toPosition()
)
}
private fun ExtsubroutineContext.toAst(): Subroutine {
val subdecl = asmsub_decl().toAst()
val constbank = constbank?.toAst()?.number?.toUInt()?.toUByte()
val varbank = varbank?.toAst()
val addr = address.toAst()
val address = Subroutine.Address(constbank, varbank, addr)
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes.toMutableList(),
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, address, true, inline = false, statements = mutableListOf(), position = toPosition()
)
}
private class AsmsubDecl(val name: String,
val parameters: List<SubroutineParameter>,
val returntypes: List<DataType>,
val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<CpuRegister>)
private fun Asmsub_declContext.toAst(): AsmsubDecl {
val name = identifier().text
val params = asmsub_params()?.toAst() ?: emptyList()
val returns = asmsub_returns()?.toAst() ?: emptyList()
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.zp, it.registerOrPair, it.position) }
val normalReturntypes = returns.map { it.type }
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers)
}
private class AsmSubroutineParameter(name: String,
type: DataType,
registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?,
position: Position) : SubroutineParameter(name, type, ZeropageWish.DONTCARE, registerOrPair, position)
private class AsmSubroutineReturn(val type: DataType,
val registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?)
private fun Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
= asmsub_return().map {
val register = it.register.text
var registerorpair: RegisterOrPair? = null
var statusregister: Statusflag? = null
if(register!=null) {
when (register) {
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(register)
in Statusflag.names -> statusregister = Statusflag.valueOf(register)
else -> throw SyntaxError("invalid register or status flag", toPosition())
}
}
// asmsubs currently only return a base datatype
val returnBaseDt = it.datatype().toAst()
AsmSubroutineReturn(
DataType.forDt(returnBaseDt),
registerorpair,
statusregister)
}
private fun Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter> = asmsub_param().map {
val vardecl = it.vardecl()
val baseDt = vardecl.datatype()?.toAst() ?: BaseDataType.UNDEFINED
var datatype = DataType.forDt(baseDt)
if(vardecl.ARRAYSIG()!=null || vardecl.arrayindex()!=null)
datatype = datatype.elementToArray()
val (registerorpair, statusregister) = parseParamRegister(it.register, it.toPosition())
val identifiers = vardecl.identifier()
if(identifiers.size>1)
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
val identifiername = identifiers[0].NAME() ?: identifiers[0].UNDERSCORENAME()
AsmSubroutineParameter(identifiername.text, datatype, registerorpair, statusregister, toPosition())
}
private fun parseParamRegister(registerTok: Token?, pos: Position): Pair<RegisterOrPair?, Statusflag?> {
if(registerTok==null)
return Pair(null, null)
val register = registerTok.text
var registerorpair: RegisterOrPair? = null
var statusregister: Statusflag? = null
if(register!=null) {
when (register) {
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(register)
in Statusflag.names -> statusregister = Statusflag.valueOf(register)
else -> {
throw SyntaxError("invalid register or status flag", Position(pos.file, registerTok.line, registerTok.charPositionInLine, registerTok.charPositionInLine+1))
}
}
}
return Pair(registerorpair, statusregister)
}
private fun Functioncall_stmtContext.toAst(): Statement {
val void = this.VOID() != null
val location = scoped_identifier().toAst()
return if(expression_list() == null)
FunctionCallStatement(location, mutableListOf(), void, toPosition())
else
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
}
private fun FunctioncallContext.toAst(): FunctionCallExpression {
val location = scoped_identifier().toAst()
return if(expression_list() == null)
FunctionCallExpression(location, mutableListOf(), toPosition())
else
FunctionCallExpression(location, expression_list().toAst().toMutableList(), toPosition())
}
private fun InlineasmContext.toAst(): InlineAssembly {
val text = INLINEASMBLOCK().text
return InlineAssembly(text.substring(2, text.length-2), false, toPosition())
}
private fun InlineirContext.toAst(): InlineAssembly {
val text = INLINEASMBLOCK().text
return InlineAssembly(text.substring(2, text.length-2), true, toPosition())
}
private fun ReturnstmtContext.toAst() : Return {
val values = if(returnvalues()==null || returnvalues().expression().isEmpty()) arrayOf() else returnvalues().expression().map { it.toAst() }.toTypedArray()
return Return(values, toPosition())
}
private fun UnconditionaljumpContext.toAst(): Jump {
return Jump(expression().toAst(), toPosition())
}
private fun LabeldefContext.toAst(): Statement =
Label(children[0].text, toPosition())
private fun AliasContext.toAst(): Statement =
Alias(identifier().text, scoped_identifier().toAst(), toPosition())
private fun SubroutineContext.toAst() : Subroutine {
// non-asm subroutine
val returntypes = sub_return_part()?.datatype()?.map { it.toAst() } ?: emptyList()
return Subroutine(
identifier().text,
sub_params()?.toAst()?.toMutableList() ?: mutableListOf(),
returntypes.map { DataType.forDt(it) }.toMutableList(),
emptyList(),
emptyList(),
emptySet(),
asmAddress = null,
isAsmSubroutine = false,
inline = false,
statements = statement_block()?.toAst() ?: mutableListOf(),
position = toPosition()
)
}
private fun Sub_paramsContext.toAst(): List<SubroutineParameter> =
sub_param().map {
val decl = it.vardecl()
val tags = decl.TAG().map { t -> t.text }
val validTags = arrayOf("@zp", "@requirezp", "@nozp", "@split", "@nosplit", "@shared")
for(tag in tags) {
if(tag !in validTags)
throw SyntaxError("invalid parameter tag '$tag'", toPosition())
}
val zp = getZpOption(tags)
val baseDt = decl.datatype()?.toAst() ?: BaseDataType.UNDEFINED
var datatype = DataType.forDt(baseDt)
if(decl.ARRAYSIG()!=null || decl.arrayindex()!=null)
datatype = datatype.elementToArray()
val identifiers = decl.identifier()
if(identifiers.size>1)
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
val identifiername = identifiers[0].NAME() ?: identifiers[0].UNDERSCORENAME()
val (registerorpair, statusregister) = parseParamRegister(it.register, it.toPosition())
if(statusregister!=null) {
throw SyntaxError("can't use status register as param for normal subroutines", Position(toPosition().file, it.register.line, it.register.charPositionInLine, it.register.charPositionInLine+1))
}
SubroutineParameter(identifiername.text, datatype, zp, registerorpair, it.toPosition())
}
private fun getZpOption(tags: List<String>): ZeropageWish = when {
"@requirezp" in tags -> ZeropageWish.REQUIRE_ZEROPAGE
"@zp" in tags -> ZeropageWish.PREFER_ZEROPAGE
"@nozp" in tags -> ZeropageWish.NOT_IN_ZEROPAGE
else -> ZeropageWish.DONTCARE
}
private fun getSplitOption(tags: List<String>): SplitWish {
return when {
"@nosplit" in tags -> SplitWish.NOSPLIT
"@split" in tags -> SplitWish.SPLIT
else -> SplitWish.DONTCARE
}
}
private fun Assign_targetContext.toAst() : AssignTarget {
return when(this) {
is IdentifierTargetContext -> {
val identifier = scoped_identifier().toAst()
AssignTarget(identifier, null, null, null, false, scoped_identifier().toPosition())
}
is MemoryTargetContext ->
AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), directmemory().toPosition()), null, false, toPosition())
is ArrayindexedTargetContext -> {
val ax = arrayindexed()
val arrayvar = ax.scoped_identifier().toAst()
val index = ax.arrayindex().toAst()
val arrayindexed = ArrayIndexedExpression(arrayvar, index, ax.toPosition())
AssignTarget(null, arrayindexed, null, null, false, toPosition())
}
is VoidTargetContext -> {
AssignTarget(null, null, null, null, true, void_().toPosition())
}
else -> throw FatalAstException("weird assign target node $this")
}
}
private fun Multi_assign_targetContext.toAst() : AssignTarget {
val targets = this.assign_target().map { it.toAst() }
return AssignTarget(null, null, null, targets, false, toPosition())
}
private fun ClobberContext.toAst() : Set<CpuRegister> {
val names = this.NAME().map { it.text }
try {
return names.map { CpuRegister.valueOf(it) }.toSet()
} catch(_: IllegalArgumentException) {
throw SyntaxError("invalid cpu register", toPosition())
}
}
private fun AssignmentContext.toAst(): Statement {
val multiAssign = multi_assign_target()
if(multiAssign!=null) {
return Assignment(multiAssign.toAst(), expression().toAst(), AssignmentOrigin.USERCODE, toPosition())
}
val nestedAssign = assignment()
return if(nestedAssign==null)
Assignment(assign_target().toAst(), expression().toAst(), AssignmentOrigin.USERCODE, toPosition())
else
ChainedAssignment(assign_target().toAst(), nestedAssign.toAst(), toPosition())
}
private fun AugassignmentContext.toAst(): Assignment {
// replace A += X with A = A + X
val target = assign_target().toAst()
val oper = operator.text.substringBefore('=')
val expression = BinaryExpression(target.toExpression(), oper, expression().toAst(), expression().toPosition())
return Assignment(assign_target().toAst(), expression, AssignmentOrigin.USERCODE, toPosition())
}
private fun DatatypeContext.toAst(): BaseDataType {
return try {
BaseDataType.valueOf(text.uppercase())
} catch (_: IllegalArgumentException) {
BaseDataType.UNDEFINED
}
}
private fun ArrayindexContext.toAst() : ArrayIndex =
ArrayIndex(expression().toAst(), toPosition())
internal fun DirectiveContext.toAst() : Directive {
if(directivenamelist() != null) {
val identifiers = directivenamelist().scoped_identifier().map { DirectiveArg(it.text, null, it.toPosition()) }
return Directive(directivename.text, identifiers, toPosition())
}
else
return Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
}
private fun DirectiveargContext.toAst() : DirectiveArg {
val str = stringliteral()
if(str!=null) {
if (str.encoding?.text != null)
throw SyntaxError("don't use a string encoding for directive arguments", toPosition())
return DirectiveArg(str.text.substring(1, text.length-1), integerliteral()?.toAst()?.number?.toUInt(), toPosition())
}
return DirectiveArg(identifier()?.text, integerliteral()?.toAst()?.number?.toUInt(), toPosition())
}
private fun IntegerliteralContext.toAst(): NumericLiteralNode {
fun makeLiteral(literalTextWithGrouping: String, radix: Int): NumericLiteralNode {
val literalText = literalTextWithGrouping.replace("_", "")
val integer: Int
var datatype = BaseDataType.UBYTE
when (radix) {
10 -> {
integer = try {
literalText.toInt()
} catch(x: NumberFormatException) {
throw SyntaxError("invalid decimal literal ${x.message}", toPosition())
}
datatype = when(integer) {
in 0..255 -> BaseDataType.UBYTE
in -128..127 -> BaseDataType.BYTE
in 0..65535 -> BaseDataType.UWORD
in -32768..32767 -> BaseDataType.WORD
in -2147483647..2147483647 -> BaseDataType.LONG
else -> BaseDataType.FLOAT
}
}
2 -> {
if(literalText.length>16)
datatype = BaseDataType.LONG
else if(literalText.length>8)
datatype = BaseDataType.UWORD
try {
integer = literalText.toInt(2)
} catch(x: NumberFormatException) {
throw SyntaxError("invalid binary literal ${x.message}", toPosition())
}
}
16 -> {
if(literalText.length>4)
datatype = BaseDataType.LONG
else if(literalText.length>2)
datatype = BaseDataType.UWORD
try {
integer = literalText.toInt(16)
} catch(x: NumberFormatException) {
throw SyntaxError("invalid hexadecimal literal ${x.message}", toPosition())
}
}
else -> throw FatalAstException("invalid radix")
}
return NumericLiteralNode(integer.toDouble(), datatype)
}
val terminal: TerminalNode = children[0] as TerminalNode
val integerPart = this.intpart.text
return when (terminal.symbol.type) {
DEC_INTEGER -> makeLiteral(integerPart, 10)
HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16)
BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2)
else -> throw FatalAstException(terminal.text)
}
}
private fun ExpressionContext.toAst(insideParentheses: Boolean=false) : Expression {
val litval = literalvalue()
if(litval!=null) {
val booleanlit = litval.booleanliteral()?.toAst()
return if(booleanlit!=null) {
NumericLiteral.fromBoolean(booleanlit, litval.toPosition())
}
else {
val intLit = litval.integerliteral()?.toAst()
when {
intLit!=null -> when(intLit.datatype) {
BaseDataType.UBYTE -> NumericLiteral(BaseDataType.UBYTE, intLit.number, litval.toPosition())
BaseDataType.BYTE -> NumericLiteral(BaseDataType.BYTE, intLit.number, litval.toPosition())
BaseDataType.UWORD -> NumericLiteral(BaseDataType.UWORD, intLit.number, litval.toPosition())
BaseDataType.WORD -> NumericLiteral(BaseDataType.WORD, intLit.number, litval.toPosition())
BaseDataType.LONG -> NumericLiteral(BaseDataType.LONG, intLit.number, litval.toPosition())
BaseDataType.FLOAT -> NumericLiteral(BaseDataType.FLOAT, intLit.number, litval.toPosition())
else -> throw FatalAstException("invalid datatype for numeric literal")
}
litval.floatliteral()!=null -> NumericLiteral(BaseDataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
litval.stringliteral()!=null -> litval.stringliteral().toAst()
litval.charliteral()!=null -> litval.charliteral().toAst()
litval.arrayliteral()!=null -> {
val array = litval.arrayliteral().toAst()
// the actual type of the arraysize can not yet be determined here
// the ConstantFold takes care of that and converts the type if needed.
ArrayLiteral(InferredTypes.InferredType.unknown(), array, position = litval.toPosition())
}
else -> throw FatalAstException("invalid parsed literal")
}
}
}
if(arrayindexed()!=null) {
val ax = arrayindexed()
val identifier = ax.scoped_identifier().toAst()
val index = ax.arrayindex().toAst()
return ArrayIndexedExpression(identifier, index, ax.toPosition())
}
if(scoped_identifier()!=null)
return scoped_identifier().toAst()
if(bop!=null) {
val operator = bop.text.trim().replace("\\s+".toRegex(), " ")
return BinaryExpression(
left.toAst(),
operator,
right.toAst(),
toPosition(),
insideParentheses = insideParentheses
)
}
if(prefix!=null)
return PrefixExpression(prefix.text, expression(0).toAst(), toPosition())
val funcall = functioncall()?.toAst()
if(funcall!=null) return funcall
if (rangefrom!=null && rangeto!=null) {
val defaultstep = if(rto.text == "to") 1 else -1
val step = rangestep?.toAst() ?: NumericLiteral.optimalInteger(defaultstep, toPosition())
return RangeExpression(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
}
if(childCount==3 && children[0].text=="(" && children[2].text==")")
return expression(0).toAst(insideParentheses=true) // expression within ( )
if(typecast()!=null) {
// typecast is always to a base datatype
val baseDt = typecast().datatype().toAst()
return TypecastExpression(expression(0).toAst(), baseDt, false, toPosition())
}
if(directmemory()!=null)
return DirectMemoryRead(directmemory().expression().toAst(), toPosition())
if(addressof()!=null) {
val addressOf = addressof()
val identifier = addressOf.scoped_identifier()
val msb = addressOf.ADDRESS_OF_MSB()!=null
// note: &< (ADDRESS_OF_LSB) is equivalent to a regular &.
return if (identifier != null)
AddressOf(addressof().scoped_identifier().toAst(), null, msb, toPosition())
else {
val array = addressOf.arrayindexed()
AddressOf(array.scoped_identifier().toAst(), array.arrayindex().toAst(), msb, toPosition())
}
}
if(if_expression()!=null) {
val ifex = if_expression()
val (condition, truevalue, falsevalue) = ifex.expression()
return IfExpression(condition.toAst(), truevalue.toAst(), falsevalue.toAst(), toPosition())
}
throw FatalAstException(text)
}
private fun CharliteralContext.toAst(): CharLiteral {
val text = this.SINGLECHAR().text
val enc = this.encoding?.text
val encoding =
if(enc!=null)
Encoding.entries.singleOrNull { it.prefix == enc }
?: throw SyntaxError("invalid encoding", toPosition())
else
Encoding.DEFAULT
val raw = text.substring(1, text.length - 1)
try {
return CharLiteral.fromEscaped(raw, encoding, toPosition())
} catch(ex: IllegalArgumentException) {
throw SyntaxError(ex.message!!, toPosition())
}
}
private fun StringliteralContext.toAst(): StringLiteral {
val text=this.STRING().text
val enc = encoding?.text
val encoding =
if(enc!=null)
Encoding.entries.singleOrNull { it.prefix == enc }
?: throw SyntaxError("invalid encoding", toPosition())
else
Encoding.DEFAULT
val raw = text.substring(1, text.length-1)
try {
return StringLiteral.fromEscaped(raw, encoding, toPosition())
} catch(ex: IllegalArgumentException) {
throw SyntaxError(ex.message!!, toPosition())
}
}
private fun Expression_listContext.toAst() = expression().map{ it.toAst() }
private fun Scoped_identifierContext.toAst() : IdentifierReference {
return IdentifierReference(identifier().map { it.text }, toPosition())
}
private fun FloatliteralContext.toAst() = text.replace("_","").toDouble()
private fun BooleanliteralContext.toAst() = when(text) {
"true" -> true
"false" -> false
else -> throw FatalAstException(text)
}
private fun ArrayliteralContext.toAst() : Array<Expression> =
expression().map { it.toAst() }.toTypedArray()
private fun If_stmtContext.toAst(): IfElse {
val condition = expression().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val elseStatements = else_part()?.toAst() ?: mutableListOf()
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
?: statement().toPosition())
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
return IfElse(condition, trueScope, elseScope, toPosition())
}
private fun Else_partContext.toAst(): MutableList<Statement> {
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
}
private fun Branch_stmtContext.toAst(): ConditionalBranch {
val branchcondition = branchcondition().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val elseStatements = else_part()?.toAst() ?: mutableListOf()
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
?: statement().toPosition())
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
return ConditionalBranch(branchcondition, trueScope, elseScope, toPosition())
}
private fun BranchconditionContext.toAst() = BranchCondition.valueOf(
text.substringAfter('_').uppercase()
)
private fun ForloopContext.toAst(): ForLoop {
val loopvar = scoped_identifier().toAst()
val iterable = expression()!!.toAst()
val scope =
if(statement()!=null)
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
else
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
return ForLoop(loopvar, iterable, scope, toPosition())
}
private fun BreakstmtContext.toAst() = Break(toPosition())
private fun ContinuestmtContext.toAst() = Continue(toPosition())
private fun DeferContext.toAst(): Defer {
val block = statement_block()?.toAst()
if(block!=null) {
val scope = AnonymousScope(block, statement_block()?.toPosition() ?: toPosition())
return Defer(scope, toPosition())
}
val singleStmt = statement()!!.toAst()
val scope = AnonymousScope(mutableListOf(singleStmt), statement().toPosition())
return Defer(scope, toPosition())
}
private fun WhileloopContext.toAst(): WhileLoop {
val condition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition())
return WhileLoop(condition, scope, toPosition())
}
private fun RepeatloopContext.toAst(): RepeatLoop {
val iterations = expression()?.toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition())
return RepeatLoop(iterations, scope, toPosition())
}
private fun UnrollloopContext.toAst(): UnrollLoop {
val iterations = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition())
return UnrollLoop(iterations, scope, toPosition())
}
private fun UntilloopContext.toAst(): UntilLoop {
val untilCondition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition())
return UntilLoop(scope, untilCondition, toPosition())
}
private fun WhenstmtContext.toAst(): When {
val condition = expression().toAst()
val choices = this.when_choice()?.map { it.toAst() }?.toMutableList() ?: mutableListOf()
return When(condition, choices, toPosition())
}
private fun When_choiceContext.toAst(): WhenChoice {
val values = expression_list()?.toAst()
val stmt = statement()?.toAst()
val stmtBlock = statement_block()?.toAst()?.toMutableList() ?: mutableListOf()
if(stmt!=null)
stmtBlock.add(stmt)
val scope = AnonymousScope(stmtBlock, toPosition())
return WhenChoice(values?.toMutableList(), scope, toPosition())
}
private fun VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl {
val tags = TAG().map { it.text }
val validTags = arrayOf("@zp", "@requirezp", "@nozp", "@split", "@nosplit", "@shared", "@alignword", "@alignpage", "@align64", "@dirty")
for(tag in tags) {
if(tag !in validTags)
throw SyntaxError("invalid variable tag '$tag'", toPosition())
}
val zp = getZpOption(tags)
val split = getSplitOption(tags)
val identifiers = identifier()
val identifiername = identifiers[0].NAME() ?: identifiers[0].UNDERSCORENAME()
val name = if(identifiers.size==1) identifiername.text else "<multiple>"
val isArray = ARRAYSIG() != null || arrayindex() != null
val alignword = "@alignword" in tags
val align64 = "@align64" in tags
val alignpage = "@alignpage" in tags
if(alignpage && alignword)
throw SyntaxError("choose a single alignment option", toPosition())
val baseDt = datatype()?.toAst() ?: BaseDataType.UNDEFINED
val dt = if(isArray) DataType.arrayFor(baseDt, split!=SplitWish.NOSPLIT) else DataType.forDt(baseDt)
return VarDecl(
type, VarDeclOrigin.USERCODE,
dt,
zp,
split,
arrayindex()?.toAst(),
name,
if(identifiers.size==1) emptyList() else identifiers.map {
val idname = it.NAME() ?: it.UNDERSCORENAME()
idname.text
},
value,
"@shared" in tags,
if(alignword) 2u else if(align64) 64u else if(alignpage) 256u else 0u,
"@dirty" in tags,
toPosition()
)
}

View File

@ -0,0 +1,750 @@
package prog8.ast.antlr
import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.Token
import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor
import org.antlr.v4.runtime.tree.TerminalNode
import prog8.ast.FatalAstException
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.SyntaxError
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.code.core.*
import prog8.code.source.SourceCode
import prog8.parser.Prog8ANTLRParser.*
import prog8.parser.Prog8ANTLRVisitor
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node>(), Prog8ANTLRVisitor<Node> {
override fun visitModule(ctx: ModuleContext): Module {
val statements = ctx.module_element().map { it.accept(this) as Statement }
return Module(statements.toMutableList(), ctx.toPosition(), source)
}
override fun visitBlock(ctx: BlockContext): Block {
val name = getname(ctx.identifier())
val address = (ctx.integerliteral()?.accept(this) as NumericLiteral?)?.number?.toUInt()
val statements = ctx.block_statement().map { it.accept(this) as Statement }
return Block(name, address, statements.toMutableList(), source.isFromLibrary, ctx.toPosition())
}
override fun visitExpression(ctx: ExpressionContext): Expression {
if(ctx.sizeof_expression!=null) {
val sdt = ctx.sizeof_argument().datatype()
val datatype = if(sdt!=null) baseDatatypeFor(sdt) else null
val expression = ctx.sizeof_argument().expression()?.accept(this) as Expression?
val sizeof = IdentifierReference(listOf("sizeof"), ctx.toPosition())
val arg = if (expression != null) expression else {
require(datatype != null)
IdentifierReference(listOf(datatype.name.lowercase()), ctx.toPosition())
}
return FunctionCallExpression(sizeof, mutableListOf(arg), ctx.toPosition())
}
if(ctx.bop!=null) {
val operator = ctx.bop.text.trim().replace("\\s+".toRegex(), " ")
return BinaryExpression(
ctx.left.accept(this) as Expression,
operator,
ctx.right.accept(this) as Expression,
ctx.toPosition()
)
}
if(ctx.prefix!=null) {
return PrefixExpression(ctx.prefix.text, ctx.expression(0).accept(this) as Expression, ctx.toPosition())
}
if(ctx.rangefrom!=null && ctx.rangeto!=null) {
val defaultstep = if(ctx.rto.text == "to") 1 else -1
return RangeExpression(
ctx.rangefrom.accept(this) as Expression,
ctx.rangeto.accept(this) as Expression,
ctx.rangestep?.accept(this) as Expression? ?: NumericLiteral.optimalInteger(defaultstep, ctx.toPosition()),
ctx.toPosition())
}
if(ctx.typecast()!=null) {
// typecast is always to a base datatype
val baseDt = baseDatatypeFor(ctx.typecast().datatype())
return TypecastExpression(ctx.expression(0).accept(this) as Expression, baseDt, false, ctx.toPosition())
}
if(ctx.childCount==3 && ctx.children[0].text=="(" && ctx.children[2].text==")")
return ctx.expression(0).accept(this) as Expression // expression within ( )
return visitChildren(ctx) as Expression
}
override fun visitSubroutinedeclaration(ctx: SubroutinedeclarationContext): Subroutine {
if(ctx.subroutine()!=null)
return ctx.subroutine().accept(this) as Subroutine
if(ctx.asmsubroutine()!=null)
return ctx.asmsubroutine().accept(this) as Subroutine
if(ctx.extsubroutine()!=null)
return ctx.extsubroutine().accept(this) as Subroutine
throw FatalAstException("weird subroutine")
}
override fun visitAlias(ctx: AliasContext): Alias {
val identifier = getname(ctx.identifier())
val target = ctx.scoped_identifier().accept(this) as IdentifierReference
return Alias(identifier, target, ctx.toPosition())
}
override fun visitDefer(ctx: DeferContext): Defer {
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
return Defer(statements, ctx.toPosition())
}
override fun visitLabeldef(ctx: LabeldefContext): Label {
return Label(getname(ctx.identifier()), ctx.toPosition())
}
override fun visitUnconditionaljump(ctx: UnconditionaljumpContext): Jump {
return Jump(ctx.expression().accept(this) as Expression, ctx.toPosition())
}
override fun visitDirective(ctx: DirectiveContext): Directive {
if(ctx.directivenamelist() != null) {
val namelist = ctx.directivenamelist().scoped_identifier().map { it.accept(this) as IdentifierReference }
val identifiers = namelist.map { DirectiveArg(it.nameInSource.joinToString("."), null, ctx.toPosition()) }
return Directive(ctx.directivename.text, identifiers, ctx.toPosition())
}
else
return Directive(ctx.directivename.text, ctx.directivearg().map { it.accept(this) as DirectiveArg }, ctx.toPosition())
}
override fun visitDirectivearg(ctx: DirectiveargContext): DirectiveArg {
val integer = (ctx.integerliteral()?.accept(this) as NumericLiteral?)?.number?.toUInt()
val str = ctx.stringliteral()
if(str!=null) {
if (str.encoding?.text != null)
throw SyntaxError("don't use a string encoding for directive arguments", ctx.toPosition())
return DirectiveArg(str.text.substring(1, str.text.length-1), integer, ctx.toPosition())
}
val identifier = ctx.identifier()?.accept(this) as IdentifierReference?
return DirectiveArg(identifier?.nameInSource?.single(), integer, ctx.toPosition())
}
override fun visitVardecl(ctx: VardeclContext): VarDecl {
val tags = ctx.TAG().map { it.text }
val validTags = arrayOf("@zp", "@requirezp", "@nozp", "@split", "@nosplit", "@shared", "@alignword", "@alignpage", "@align64", "@dirty")
for(tag in tags) {
if(tag !in validTags)
throw SyntaxError("invalid variable tag '$tag'", ctx.toPosition())
}
val zp = getZpOption(tags)
val split = getSplitOption(tags)
val alignword = "@alignword" in tags
val align64 = "@align64" in tags
val alignpage = "@alignpage" in tags
if(alignpage && alignword)
throw SyntaxError("choose a single alignment option", ctx.toPosition())
val identifiers = ctx.identifier().map { getname(it) }
val identifiername = identifiers[0]
val name = if(identifiers.size==1) identifiername else "<multiple>"
val arrayIndex = ctx.arrayindex()?.accept(this) as ArrayIndex?
val isArray = ctx.ARRAYSIG() != null || arrayIndex != null
val baseDt = baseDatatypeFor(ctx.datatype())
val dt = if(isArray) DataType.arrayFor(baseDt, split!=SplitWish.NOSPLIT) else DataType.forDt(baseDt)
return VarDecl(
VarDeclType.VAR, // can be changed to MEMORY or CONST as required
VarDeclOrigin.USERCODE,
dt,
zp,
split,
arrayIndex,
name,
if(identifiers.size==1) emptyList() else identifiers,
null,
"@shared" in tags,
if(alignword) 2u else if(align64) 64u else if(alignpage) 256u else 0u,
"@dirty" in tags,
ctx.toPosition()
)
}
override fun visitVarinitializer(ctx: VarinitializerContext): VarDecl {
val vardecl = ctx.vardecl().accept(this) as VarDecl
vardecl.value = ctx.expression().accept(this) as Expression
return vardecl
}
override fun visitConstdecl(ctx: ConstdeclContext): VarDecl {
val vardecl = ctx.varinitializer().accept(this) as VarDecl
vardecl.type = VarDeclType.CONST
return vardecl
}
override fun visitMemoryvardecl(ctx: MemoryvardeclContext): VarDecl {
val vardecl = ctx.varinitializer().accept(this) as VarDecl
vardecl.type = VarDeclType.MEMORY
return vardecl
}
override fun visitArrayindex(ctx: ArrayindexContext): ArrayIndex {
return ArrayIndex(ctx.expression().accept(this) as Expression, ctx.toPosition())
}
override fun visitAssignment(ctx: AssignmentContext): Statement {
val multiAssign = ctx.multi_assign_target()
if(multiAssign!=null) {
return Assignment(multiAssign.accept(this) as AssignTarget, ctx.expression().accept(this) as Expression, AssignmentOrigin.USERCODE, ctx.toPosition())
}
val nestedAssign = ctx.assignment()
return if(nestedAssign==null)
Assignment(ctx.assign_target().accept(this) as AssignTarget, ctx.expression().accept(this) as Expression, AssignmentOrigin.USERCODE, ctx.toPosition())
else
ChainedAssignment(ctx.assign_target().accept(this) as AssignTarget, nestedAssign.accept(this) as Statement, ctx.toPosition())
}
override fun visitAugassignment(ctx: AugassignmentContext): Assignment {
// replace A += X with A = A + X
val target = ctx.assign_target().accept(this) as AssignTarget
val oper = ctx.operator.text.substringBefore('=')
val expression = BinaryExpression(target.toExpression(), oper, ctx.expression().accept(this) as Expression, ctx.toPosition())
return Assignment(target, expression, AssignmentOrigin.USERCODE, ctx.toPosition())
}
override fun visitIdentifierTarget(ctx: IdentifierTargetContext): AssignTarget {
val identifier = ctx.scoped_identifier().accept(this) as IdentifierReference
return AssignTarget(identifier, null, null, null, false, ctx.toPosition())
}
override fun visitArrayindexedTarget(ctx: ArrayindexedTargetContext): AssignTarget {
val ax = ctx.arrayindexed()
val arrayvar = ax.scoped_identifier().accept(this) as IdentifierReference
val index = ax.arrayindex().accept(this) as ArrayIndex
val arrayindexed = ArrayIndexedExpression(arrayvar, index, ax.toPosition())
return AssignTarget(null, arrayindexed, null, null, false, ctx.toPosition())
}
override fun visitMemoryTarget(ctx: MemoryTargetContext): AssignTarget {
return AssignTarget(null, null,
DirectMemoryWrite(ctx.directmemory().expression().accept(this) as Expression, ctx.toPosition()),
null, false, ctx.toPosition())
}
override fun visitVoidTarget(ctx: VoidTargetContext): AssignTarget {
return AssignTarget(null, null, null, null, true, ctx.toPosition())
}
override fun visitMulti_assign_target(ctx: Multi_assign_targetContext): AssignTarget {
val targets = ctx.assign_target().map { it.accept(this) as AssignTarget }
return AssignTarget(null, null, null, targets, false, ctx.toPosition())
}
override fun visitPostincrdecr(ctx: PostincrdecrContext): Assignment {
val tgt = ctx.assign_target().accept(this) as AssignTarget
val operator = ctx.operator.text
val pos = ctx.toPosition()
val addSubOne = BinaryExpression(tgt.toExpression(), if(operator=="++") "+" else "-", NumericLiteral.optimalInteger(1, pos), pos)
return Assignment(tgt, addSubOne, AssignmentOrigin.USERCODE, pos)
}
override fun visitArrayindexed(ctx: ArrayindexedContext): ArrayIndexedExpression {
val identifier = ctx.scoped_identifier().accept(this) as IdentifierReference
val index = ctx.arrayindex().accept(this) as ArrayIndex
return ArrayIndexedExpression(identifier, index, ctx.toPosition())
}
override fun visitDirectmemory(ctx: DirectmemoryContext): DirectMemoryRead {
return DirectMemoryRead(ctx.expression().accept(this) as Expression, ctx.toPosition())
}
override fun visitAddressof(ctx: AddressofContext): AddressOf {
val identifier = ctx.scoped_identifier()?.accept(this) as IdentifierReference?
val msb = ctx.ADDRESS_OF_MSB()!=null
// note: &< (ADDRESS_OF_LSB) is equivalent to a regular &.
return if (identifier != null)
AddressOf(identifier, null, msb, ctx.toPosition())
else {
val array = ctx.arrayindexed()
AddressOf(array.scoped_identifier().accept(this) as IdentifierReference,
array.arrayindex().accept(this) as ArrayIndex,
msb, ctx.toPosition())
}
}
override fun visitFunctioncall(ctx: FunctioncallContext): FunctionCallExpression {
val name = ctx.scoped_identifier().accept(this) as IdentifierReference
val args = ctx.expression_list()?.expression()?.map { it.accept(this) as Expression } ?: emptyList()
return FunctionCallExpression(name, args.toMutableList(), ctx.toPosition())
}
override fun visitFunctioncall_stmt(ctx: Functioncall_stmtContext): FunctionCallStatement {
val void = ctx.VOID() != null
val name = ctx.scoped_identifier().accept(this) as IdentifierReference
val args = ctx.expression_list()?.expression()?.map { it.accept(this) as Expression } ?: emptyList()
return FunctionCallStatement(name, args.toMutableList(), void, ctx.toPosition())
}
override fun visitReturnstmt(ctx: ReturnstmtContext): Return {
val cvalues = ctx.returnvalues()
val values = if(cvalues==null || cvalues.expression().isEmpty()) arrayOf() else cvalues.expression().map { it.accept(this) as Expression }.toTypedArray()
return Return(values, ctx.toPosition())
}
override fun visitBreakstmt(ctx: BreakstmtContext): Break {
return Break(ctx.toPosition())
}
override fun visitContinuestmt(ctx: ContinuestmtContext): Continue {
return Continue(ctx.toPosition())
}
override fun visitIdentifier(ctx: IdentifierContext): IdentifierReference {
return IdentifierReference(listOf(getname(ctx)), ctx.toPosition())
}
override fun visitScoped_identifier(ctx: Scoped_identifierContext): IdentifierReference {
val children = ctx.identifier().map { it.text }
return IdentifierReference(children, ctx.toPosition())
}
override fun visitIntegerliteral(ctx: IntegerliteralContext): NumericLiteral {
fun makeLiteral(literalTextWithGrouping: String, radix: Int): Pair<Double, BaseDataType> {
val literalText = literalTextWithGrouping.replace("_", "")
val integer: Int
var datatype = BaseDataType.UBYTE
when (radix) {
10 -> {
integer = try {
literalText.toInt()
} catch(x: NumberFormatException) {
throw SyntaxError("invalid decimal literal ${x.message}", ctx.toPosition())
}
datatype = when(integer) {
in 0..255 -> BaseDataType.UBYTE
in -128..127 -> BaseDataType.BYTE
in 0..65535 -> BaseDataType.UWORD
in -32768..32767 -> BaseDataType.WORD
in -2147483647..2147483647 -> BaseDataType.LONG
else -> BaseDataType.FLOAT
}
}
2 -> {
if(literalText.length>16)
datatype = BaseDataType.LONG
else if(literalText.length>8)
datatype = BaseDataType.UWORD
try {
integer = literalText.toInt(2)
} catch(x: NumberFormatException) {
throw SyntaxError("invalid binary literal ${x.message}", ctx.toPosition())
}
}
16 -> {
if(literalText.length>4)
datatype = BaseDataType.LONG
else if(literalText.length>2)
datatype = BaseDataType.UWORD
try {
integer = literalText.toInt(16)
} catch(x: NumberFormatException) {
throw SyntaxError("invalid hexadecimal literal ${x.message}", ctx.toPosition())
}
}
else -> throw FatalAstException("invalid radix")
}
return integer.toDouble() to datatype
}
val terminal: TerminalNode = ctx.children[0] as TerminalNode
val integerPart = ctx.intpart.text
val integer = when (terminal.symbol.type) {
DEC_INTEGER -> makeLiteral(integerPart, 10)
HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16)
BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2)
else -> throw FatalAstException(terminal.text)
}
return NumericLiteral(integer.second, integer.first, ctx.toPosition())
}
override fun visitBooleanliteral(ctx: BooleanliteralContext): NumericLiteral {
val boolean = when(ctx.text) {
"true" -> true
"false" -> false
else -> throw FatalAstException(ctx.text)
}
return NumericLiteral.fromBoolean(boolean, ctx.toPosition())
}
override fun visitArrayliteral(ctx: ArrayliteralContext): ArrayLiteral {
val array = ctx.expression().map { it.accept(this) as Expression }.toTypedArray()
// the actual type of the arraysize can not yet be determined here
// the ConstantFold takes care of that and converts the type if needed.
return ArrayLiteral(InferredTypes.InferredType.unknown(), array, position = ctx.toPosition())
}
override fun visitStringliteral(ctx: StringliteralContext): StringLiteral {
val text = ctx.STRING().text
val enc = ctx.encoding?.text
val encoding =
if(enc!=null)
Encoding.entries.singleOrNull { it.prefix == enc }
?: throw SyntaxError("invalid encoding", ctx.toPosition())
else
Encoding.DEFAULT
val raw = text.substring(1, text.length-1)
try {
return StringLiteral.fromEscaped(raw, encoding, ctx.toPosition())
} catch(ex: IllegalArgumentException) {
throw SyntaxError(ex.message!!, ctx.toPosition())
}
}
override fun visitCharliteral(ctx: CharliteralContext): CharLiteral {
val text = ctx.SINGLECHAR().text
val enc = ctx.encoding?.text
val encoding =
if(enc!=null)
Encoding.entries.singleOrNull { it.prefix == enc }
?: throw SyntaxError("invalid encoding", ctx.toPosition())
else
Encoding.DEFAULT
val raw = text.substring(1, text.length - 1)
try {
return CharLiteral.fromEscaped(raw, encoding, ctx.toPosition())
} catch(ex: IllegalArgumentException) {
throw SyntaxError(ex.message!!, ctx.toPosition())
}
}
override fun visitFloatliteral(ctx: FloatliteralContext): NumericLiteral {
val floatvalue = ctx.text.replace("_","").toDouble()
return NumericLiteral(BaseDataType.FLOAT, floatvalue, ctx.toPosition())
}
override fun visitInlineasm(ctx: InlineasmContext): InlineAssembly {
val text = ctx.INLINEASMBLOCK().text
return InlineAssembly(text.substring(2, text.length-2), false, ctx.toPosition())
}
override fun visitInlineir(ctx: InlineirContext): InlineAssembly {
val text = ctx.INLINEASMBLOCK().text
return InlineAssembly(text.substring(2, text.length-2), true, ctx.toPosition())
}
override fun visitSubroutine(ctx: SubroutineContext): Subroutine {
val name = getname(ctx.identifier())
val parameters = ctx.sub_params()?.sub_param()?.map { it.accept(this) as SubroutineParameter } ?: emptyList()
val returntypes = ctx.sub_return_part()?.datatype()?. map { dataTypeFor(it) } ?: emptyList()
val statements = ctx.statement_block().accept(this) as AnonymousScope
return Subroutine(
name,
parameters.toMutableList(),
returntypes.toMutableList(),
emptyList(),
emptyList(),
emptySet(),
asmAddress = null,
isAsmSubroutine = false,
inline = false,
statements = statements.statements,
position = ctx.toPosition()
)
}
override fun visitStatement_block(ctx: Statement_blockContext): AnonymousScope {
val statements = ctx.statement().map { it.accept(this) as Statement }
return AnonymousScope(statements.toMutableList(), ctx.toPosition())
}
override fun visitSub_param(pctx: Sub_paramContext): SubroutineParameter {
val decl = pctx.vardecl()
val tags = decl.TAG().map { t -> t.text }
val validTags = arrayOf("@zp", "@requirezp", "@nozp", "@split", "@nosplit", "@shared")
for(tag in tags) {
if(tag !in validTags)
throw SyntaxError("invalid parameter tag '$tag'", pctx.toPosition())
}
val zp = getZpOption(tags)
val decldt = decl.datatype()
var datatype = if(decldt!=null) dataTypeFor(decldt) else DataType.UNDEFINED
if(decl.ARRAYSIG()!=null || decl.arrayindex()!=null)
datatype = datatype.elementToArray()
val identifiers = decl.identifier()
if(identifiers.size>1)
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
val identifiername = getname(identifiers[0])
val (registerorpair, statusregister) = parseParamRegister(pctx.register, pctx.toPosition())
if(statusregister!=null) {
throw SyntaxError("can't use status register as param for normal subroutines", Position(pctx.toPosition().file, pctx.register.line, pctx.register.charPositionInLine, pctx.register.charPositionInLine+1))
}
return SubroutineParameter(identifiername, datatype, zp, registerorpair, pctx.toPosition())
}
override fun visitAsmsubroutine(ctx: AsmsubroutineContext): Subroutine {
val inline = ctx.INLINE()!=null
val ad = asmSubDecl(ctx.asmsub_decl())
val statements = ctx.statement_block().accept(this) as AnonymousScope
return Subroutine(ad.name,
ad.parameters.toMutableList(),
ad.returntypes.toMutableList(),
ad.asmParameterRegisters,
ad.asmReturnvaluesRegisters,
ad.asmClobbers, null, true, inline,
statements = statements.statements, position = ctx.toPosition()
)
}
override fun visitExtsubroutine(ctx: ExtsubroutineContext): Subroutine {
val subdecl = asmSubDecl(ctx.asmsub_decl())
val constbank = (ctx.constbank?.accept(this) as NumericLiteral?)?.number?.toUInt()?.toUByte()
val varbank = ctx.varbank?.accept(this) as IdentifierReference?
val addr = ctx.address.accept(this) as Expression
val address = Subroutine.Address(constbank, varbank, addr)
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes.toMutableList(),
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, address, true, inline = false, statements = mutableListOf(), position = ctx.toPosition()
)
}
override fun visitIf_stmt(ctx: If_stmtContext): IfElse {
val condition = ctx.expression().accept(this) as Expression
val truepart = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
val elsepart = ctx.else_part()?.accept(this) as AnonymousScope? ?: AnonymousScope.empty()
return IfElse(condition, truepart, elsepart, ctx.toPosition())
}
override fun visitElse_part(ctx: Else_partContext): AnonymousScope {
return stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
}
override fun visitIf_expression(ctx: If_expressionContext): IfExpression {
val (condition, truevalue, falsevalue) = ctx.expression().map { it.accept(this) as Expression }
return IfExpression(condition, truevalue, falsevalue, ctx.toPosition())
}
override fun visitBranch_stmt(ctx: Branch_stmtContext): ConditionalBranch {
val branchcondition = branchCondition(ctx.branchcondition())
val truepart = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
val elsepart = ctx.else_part()?.accept(this) as AnonymousScope? ?: AnonymousScope.empty()
return ConditionalBranch(branchcondition, truepart, elsepart, ctx.toPosition())
}
override fun visitForloop(ctx: ForloopContext): ForLoop {
val loopvar = ctx.scoped_identifier().accept(this) as IdentifierReference
val iterable = ctx.expression().accept(this) as Expression
val scope = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
return ForLoop(loopvar, iterable, scope, ctx.toPosition())
}
override fun visitWhileloop(ctx: WhileloopContext): WhileLoop {
val condition = ctx.expression().accept(this) as Expression
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
return WhileLoop(condition, statements, ctx.toPosition())
}
override fun visitUntilloop(ctx: UntilloopContext): UntilLoop {
val condition = ctx.expression().accept(this) as Expression
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
return UntilLoop(statements, condition, ctx.toPosition())
}
override fun visitRepeatloop(ctx: RepeatloopContext): RepeatLoop {
val iterations = ctx.expression()?.accept(this) as Expression?
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
return RepeatLoop(iterations, statements, ctx.toPosition())
}
override fun visitUnrollloop(ctx: UnrollloopContext): UnrollLoop {
val iterations = ctx.expression().accept(this) as Expression
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
return UnrollLoop(iterations, statements, ctx.toPosition())
}
override fun visitWhenstmt(ctx: WhenstmtContext): When {
val condition = ctx.expression().accept(this) as Expression
val choices = ctx.when_choice()?.map { it.accept(this) as WhenChoice }?.toMutableList() ?: mutableListOf()
return When(condition, choices, ctx.toPosition())
}
override fun visitWhen_choice(ctx: When_choiceContext): WhenChoice {
val values = ctx.expression_list()?.expression()?.map { it.accept(this) as Expression }
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
return WhenChoice(values?.toMutableList(), statements, ctx.toPosition())
}
override fun visitOngoto(ctx: OngotoContext): OnGoto {
val elsepart = ctx.else_part()?.accept(this) as AnonymousScope? ?: AnonymousScope.empty()
val isCall = ctx.kind.text == "call"
val index = ctx.expression().accept(this) as Expression
val labels = ctx.directivenamelist().scoped_identifier().map { it.accept(this) as IdentifierReference }
return OnGoto(isCall, index, labels, elsepart, ctx.toPosition())
}
override fun visitModule_element(ctx: Module_elementContext): Node = visitChildren(ctx)
override fun visitBlock_statement(ctx: Block_statementContext): Statement = visitChildren(ctx) as Statement
override fun visitStatement(ctx: StatementContext): Statement = visitChildren(ctx) as Statement
override fun visitVariabledeclaration(ctx: VariabledeclarationContext): VarDecl = visitChildren(ctx) as VarDecl
override fun visitLiteralvalue(ctx: LiteralvalueContext): Expression = visitChildren(ctx) as Expression
override fun visitDirectivenamelist(ctx: DirectivenamelistContext) = throw FatalAstException("should not be called")
override fun visitAsmsub_decl(ctx: Asmsub_declContext?) = throw FatalAstException("should not be called")
override fun visitAsmsub_params(ctx: Asmsub_paramsContext) = throw FatalAstException("should not be called")
override fun visitExpression_list(ctx: Expression_listContext) = throw FatalAstException("should not be called")
override fun visitBranchcondition(ctx: BranchconditionContext) = throw FatalAstException("should not be called")
override fun visitDatatype(ctx: DatatypeContext) = throw FatalAstException("should not be called")
override fun visitSizeof_argument(ctx: Sizeof_argumentContext) = throw FatalAstException("should not be called")
override fun visitReturnvalues(ctx: ReturnvaluesContext) = throw FatalAstException("should not be called")
override fun visitTypecast(ctx: TypecastContext) = throw FatalAstException("should not be called")
override fun visitSub_params(ctx: Sub_paramsContext) = throw FatalAstException("should not be called")
override fun visitAsmsub_param(ctx: Asmsub_paramContext) = throw FatalAstException("should not be called")
override fun visitAsmsub_clobbers(ctx: Asmsub_clobbersContext) = throw FatalAstException("should not be called")
override fun visitClobber(ctx: ClobberContext) = throw FatalAstException("should not be called")
override fun visitAsmsub_returns(ctx: Asmsub_returnsContext) = throw FatalAstException("should not be called")
override fun visitAsmsub_return(ctx: Asmsub_returnContext) = throw FatalAstException("should not be called")
override fun visitSub_return_part(ctx: Sub_return_partContext) = throw FatalAstException("should not be called")
private fun getname(identifier: IdentifierContext): String = identifier.children[0].text
private fun ParserRuleContext.toPosition() : Position {
val pathString = start.inputStream.sourceName
val filename = if(SourceCode.isRegularFilesystemPath(pathString)) {
val path = Path(pathString)
if(path.isRegularFile()) {
SourceCode.relative(path).toString()
} else {
path.toString()
}
} else {
pathString
}
// note: beware of TAB characters in the source text, they count as 1 column...
return Position(filename, start.line, start.charPositionInLine+1, start.charPositionInLine + 1 + start.stopIndex - start.startIndex)
}
private fun getZpOption(tags: List<String>): ZeropageWish = when {
"@requirezp" in tags -> ZeropageWish.REQUIRE_ZEROPAGE
"@zp" in tags -> ZeropageWish.PREFER_ZEROPAGE
"@nozp" in tags -> ZeropageWish.NOT_IN_ZEROPAGE
else -> ZeropageWish.DONTCARE
}
private fun getSplitOption(tags: List<String>): SplitWish {
return when {
"@nosplit" in tags -> SplitWish.NOSPLIT
"@split" in tags -> SplitWish.SPLIT
else -> SplitWish.DONTCARE
}
}
private fun asmSubroutineParam(pctx: Asmsub_paramContext): AsmSubroutineParameter {
val vardecl = pctx.vardecl()
val decldt = vardecl.datatype()
var datatype = if(decldt!=null) dataTypeFor(decldt) else DataType.UNDEFINED
if(vardecl.ARRAYSIG()!=null || vardecl.arrayindex()!=null)
datatype = datatype.elementToArray()
val (registerorpair, statusregister) = parseParamRegister(pctx.register, pctx.toPosition())
val identifiers = vardecl.identifier()
if(identifiers.size>1)
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
val identifiername = getname(identifiers[0])
return AsmSubroutineParameter(identifiername, datatype, registerorpair, statusregister, pctx.toPosition())
}
private fun parseParamRegister(registerTok: Token?, pos: Position): Pair<RegisterOrPair?, Statusflag?> {
if(registerTok==null)
return Pair(null, null)
val register = registerTok.text
var registerorpair: RegisterOrPair? = null
var statusregister: Statusflag? = null
if(register!=null) {
when (register) {
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(register)
in Statusflag.names -> statusregister = Statusflag.valueOf(register)
else -> {
throw SyntaxError("invalid register or status flag", Position(pos.file, registerTok.line, registerTok.charPositionInLine, registerTok.charPositionInLine+1))
}
}
}
return Pair(registerorpair, statusregister)
}
private fun asmReturn(rctx: Asmsub_returnContext): AsmSubroutineReturn {
val register = rctx.register.text
var registerorpair: RegisterOrPair? = null
var statusregister: Statusflag? = null
if(register!=null) {
when (register) {
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(register)
in Statusflag.names -> statusregister = Statusflag.valueOf(register)
else -> throw SyntaxError("invalid register or status flag", rctx.toPosition())
}
}
return AsmSubroutineReturn(
dataTypeFor(rctx.datatype()),
registerorpair,
statusregister)
}
private fun cpuRegister(text: String, pos: Position): CpuRegister {
try {
return CpuRegister.valueOf(text)
} catch(_: IllegalArgumentException) {
throw SyntaxError("invalid cpu register", pos)
}
}
private fun dataTypeFor(it: DatatypeContext) = DataType.forDt(baseDatatypeFor(it))
private fun baseDatatypeFor(it: DatatypeContext) = BaseDataType.valueOf(it.text.uppercase())
private fun stmtBlockOrSingle(statementBlock: Statement_blockContext?, statement: StatementContext?): AnonymousScope {
return if(statementBlock!=null)
statementBlock.accept(this) as AnonymousScope
else if(statement!=null)
AnonymousScope(mutableListOf(statement.accept(this) as Statement), statement.toPosition())
else
AnonymousScope.empty()
}
private fun branchCondition(ctx: BranchconditionContext) = BranchCondition.valueOf(ctx.text.substringAfter('_').uppercase())
private fun asmSubDecl(ad: Asmsub_declContext): AsmsubDecl {
val name = getname(ad.identifier())
val params = ad.asmsub_params()?.asmsub_param()?.map { asmSubroutineParam(it) } ?: emptyList()
val returns = ad.asmsub_returns()?.asmsub_return()?.map { asmReturn(it) } ?: emptyList()
val clobbers = ad.asmsub_clobbers()?.clobber()?.NAME()?.map { cpuRegister(it.text, ad.toPosition()) } ?: emptyList()
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.zp, it.registerOrPair, it.position) }
val normalReturntypes = returns.map { it.type }
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers.toSet())
}
private class AsmsubDecl(val name: String,
val parameters: List<SubroutineParameter>,
val returntypes: List<DataType>,
val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<CpuRegister>)
private class AsmSubroutineParameter(name: String,
type: DataType,
registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?,
position: Position) : SubroutineParameter(name, type, ZeropageWish.DONTCARE, registerOrPair, position)
private class AsmSubroutineReturn(val type: DataType,
val registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?)
}

View File

@ -151,8 +151,7 @@ class BinaryExpression(
var left: Expression,
var operator: String,
var right: Expression,
override val position: Position,
private val insideParentheses: Boolean = false
override val position: Position
) : Expression() {
override lateinit var parent: Node
@ -172,7 +171,7 @@ class BinaryExpression(
replacement.parent = this
}
override fun copy() = BinaryExpression(left.copy(), operator, right.copy(), position, insideParentheses)
override fun copy() = BinaryExpression(left.copy(), operator, right.copy(), position)
override fun toString() = "[$left $operator $right]"
override val isSimple = false
@ -509,30 +508,24 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
}
class NumericLiteral(val type: BaseDataType, // only numerical types allowed + bool (there is no separate BooleanLiteral node)
numbervalue: Double, // can be byte, word or float depending on the type
val number: Double, // can be byte, word or float depending on the type
override val position: Position) : Expression() {
override lateinit var parent: Node
val number: Double by lazy {
if(type==BaseDataType.FLOAT)
numbervalue
else {
val trunc = truncate(numbervalue)
if(trunc != numbervalue)
throw ExpressionError("refused truncating of float to avoid loss of precision", position)
trunc
}
}
init {
when(type) {
BaseDataType.UBYTE -> require(numbervalue in 0.0..255.0)
BaseDataType.BYTE -> require(numbervalue in -128.0..127.0)
BaseDataType.UWORD -> require(numbervalue in 0.0..65535.0)
BaseDataType.WORD -> require(numbervalue in -32768.0..32767.0)
BaseDataType.LONG -> require(numbervalue in -2147483647.0..2147483647.0)
BaseDataType.BOOL -> require(numbervalue==0.0 || numbervalue==1.0)
BaseDataType.UBYTE -> require(number in 0.0..255.0)
BaseDataType.BYTE -> require(number in -128.0..127.0)
BaseDataType.UWORD -> require(number in 0.0..65535.0)
BaseDataType.WORD -> require(number in -32768.0..32767.0)
BaseDataType.LONG -> require(number in -2147483647.0..2147483647.0)
BaseDataType.BOOL -> require(number==0.0 || number==1.0)
else -> require(type.isNumericOrBool) { "numeric literal type should be numeric or bool: $type" }
}
if(type!=BaseDataType.FLOAT) {
if(truncate(number) != number)
throw ExpressionError("float value given for integer datatype", position)
}
}
override val isSimple = true
@ -749,16 +742,44 @@ class NumericLiteral(val type: BaseDataType, // only numerical types allowed
}
BaseDataType.FLOAT -> {
try {
if (targettype == BaseDataType.BYTE && number >= -128 && number <= 127)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if (targettype == BaseDataType.UBYTE && number >= 0 && number <= 255)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if (targettype == BaseDataType.WORD && number >= -32768 && number <= 32767)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if (targettype == BaseDataType.UWORD && number >= 0 && number <= 65535)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if(targettype==BaseDataType.LONG && number >=0 && number <= 2147483647)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
when (targettype) {
BaseDataType.BYTE if number >= -128 && number <= 127 -> {
val converted = number.toInt().toByte().toDouble()
if(implicit && converted!=number)
return ValueAfterCast(false, "refused truncating of float to avoid loss of precision", this)
else
return ValueAfterCast(true, null, NumericLiteral(targettype, converted, position))
}
BaseDataType.UBYTE if number >= 0 && number <= 255 -> {
val converted = number.toInt().toUByte().toDouble()
if(implicit && converted!=number)
return ValueAfterCast(false, "refused truncating of float to avoid loss of precision", this)
else
return ValueAfterCast(true, null, NumericLiteral(targettype, converted, position))
}
BaseDataType.WORD if number >= -32768 && number <= 32767 -> {
val converted = number.toInt().toShort().toDouble()
if(implicit && converted!=number)
return ValueAfterCast(false, "refused truncating of float to avoid loss of precision", this)
else
return ValueAfterCast(true, null, NumericLiteral(targettype, converted, position))
}
BaseDataType.UWORD if number >= 0 && number <= 65535 -> {
val converted = number.toInt().toUShort().toDouble()
if(implicit && converted!=number)
return ValueAfterCast(false, "refused truncating of float to avoid loss of precision", this)
else
return ValueAfterCast(true, null, NumericLiteral(targettype, converted, position))
}
BaseDataType.LONG if number >=0 && number <= 2147483647 -> {
val converted = number.toInt().toDouble()
if(implicit && converted!=number)
return ValueAfterCast(false, "refused truncating of float to avoid loss of precision", this)
else
return ValueAfterCast(true, null, NumericLiteral(targettype, converted, position))
}
else -> {}
}
} catch (x: ExpressionError) {
return ValueAfterCast(false, x.message,null)
}

View File

@ -239,19 +239,20 @@ enum class VarDeclType {
MEMORY
}
class VarDecl(val type: VarDeclType,
val origin: VarDeclOrigin,
val datatype: DataType,
val zeropage: ZeropageWish,
val splitwordarray: SplitWish,
var arraysize: ArrayIndex?,
override val name: String,
val names: List<String>,
var value: Expression?,
val sharedWithAsm: Boolean,
val alignment: UInt,
val dirty: Boolean,
override val position: Position) : Statement(), INamedStatement {
class VarDecl(
var type: VarDeclType,
val origin: VarDeclOrigin,
val datatype: DataType,
val zeropage: ZeropageWish,
val splitwordarray: SplitWish,
var arraysize: ArrayIndex?,
override val name: String,
val names: List<String>,
var value: Expression?,
val sharedWithAsm: Boolean,
val alignment: UInt,
val dirty: Boolean,
override val position: Position) : Statement(), INamedStatement {
override lateinit var parent: Node
var allowInitializeWithZero = true
@ -284,7 +285,7 @@ class VarDecl(val type: VarDeclType,
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
var arrayDt = array.type.getOrElse { throw FatalAstException("unknown dt") }
if(arrayDt.isSplitWordArray) {
// autovars for array literals are NOT stored as a split word array!
// autovars for array literals are NEVER stored as a split word array!
when(arrayDt.sub) {
BaseDataType.WORD -> arrayDt = DataType.arrayFor(BaseDataType.WORD, false)
BaseDataType.UWORD -> arrayDt = DataType.arrayFor(BaseDataType.UWORD, false)
@ -296,6 +297,24 @@ class VarDecl(val type: VarDeclType,
SplitWish.NOSPLIT, arraysize, autoVarName, emptyList(), array,
sharedWithAsm = false, alignment = 0u, dirty = false, position = array.position)
}
fun createAutoOptionalSplit(array: ArrayLiteral): VarDecl {
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
val arrayDt = array.type.getOrElse { throw FatalAstException("unknown dt") }
val split = if(arrayDt.isSplitWordArray) SplitWish.SPLIT else if(arrayDt.isWordArray) SplitWish.NOSPLIT else SplitWish.DONTCARE
val arraysize = ArrayIndex.forArray(array)
return VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, arrayDt, ZeropageWish.NOT_IN_ZEROPAGE,
split, arraysize, autoVarName, emptyList(), array,
sharedWithAsm = false, alignment = 0u, dirty = false, position = array.position)
}
fun createAuto(dt: DataType): VarDecl {
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
val vardecl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, dt, ZeropageWish.NOT_IN_ZEROPAGE,
SplitWish.DONTCARE, null, autoVarName, emptyList(), null,
sharedWithAsm = false, alignment = 0u, dirty = false, position = Position.DUMMY)
return vardecl
}
}
val isArray: Boolean
@ -812,6 +831,10 @@ class AnonymousScope(override val statements: MutableList<Statement>,
statements.forEach { it.linkParents(this) }
}
companion object {
fun empty(pos: Position?=null): AnonymousScope = AnonymousScope(mutableListOf(), pos ?: Position.DUMMY)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement)
val idx = statements.indexOfFirst { it===node }
@ -1177,6 +1200,30 @@ class When(var condition: Expression,
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>): Boolean =
condition.referencesIdentifier(nameInSource) || choices.any { it.referencesIdentifier(nameInSource) }
fun betterAsOnGoto(program: Program, compilerOptions: CompilationOptions): Boolean {
// a when that has only goto's and the values 0,1,2,3,4... is better written as a on..goto
val sizeLimit = if(compilerOptions.compTarget.cpu == CpuType.CPU65C02) 4 else 6
if(choices.size >= sizeLimit) {
if (condition.inferType(program).isBytes) {
if (choices.all { (it.statements.statements.singleOrNull() as? Jump)?.target is IdentifierReference }) {
val values = choices.flatMap {
it.values ?: mutableListOf()
}.map {
it.constValue(program)?.number?.toInt()
}
if(null !in values) {
val sortedValues = values.filterNotNull().sorted()
val range = IntRange(sortedValues.first(), sortedValues.last())
if(range.toList() == sortedValues) {
return true
}
}
}
}
}
return false
}
}
class WhenChoice(var values: MutableList<Expression>?, // if null, this is the 'else' part
@ -1233,3 +1280,29 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
override fun copy() = DirectMemoryWrite(addressExpression.copy(), position)
override fun referencesIdentifier(nameInSource: List<String>): Boolean = addressExpression.referencesIdentifier(nameInSource)
}
class OnGoto(
val isCall: Boolean,
val index: Expression,
val labels: List<IdentifierReference>,
val elsepart: AnonymousScope?,
override val position: Position
) : Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
index.linkParents(this)
labels.forEach { it.linkParents(this) }
elsepart?.linkParents(this)
}
override fun copy(): OnGoto = OnGoto(isCall, index.copy(), labels.map { it.copy() }, elsepart?.copy(), position)
override fun referencesIdentifier(nameInSource: List<String>) = index.referencesIdentifier(nameInSource) || labels.any { it.referencesIdentifier(nameInSource) } || elsepart?.referencesIdentifier(nameInSource) == true
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace")
}
}

View File

@ -137,6 +137,7 @@ abstract class AstWalker {
open fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = noModifications
open fun before(whenStmt: When, parent: Node): Iterable<IAstModification> = noModifications
open fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun before(ongoto: OnGoto, parent: Node): Iterable<IAstModification> = noModifications
open fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = noModifications
open fun after(array: ArrayLiteral, parent: Node): Iterable<IAstModification> = noModifications
@ -182,6 +183,7 @@ abstract class AstWalker {
open fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = noModifications
open fun after(whenStmt: When, parent: Node): Iterable<IAstModification> = noModifications
open fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun after(ongoto: OnGoto, parent: Node): Iterable<IAstModification> = noModifications
protected val modifications = mutableListOf<Triple<IAstModification, Node, Node>>()
@ -502,5 +504,13 @@ abstract class AstWalker {
chainedAssignment.nested.accept(this, chainedAssignment)
track(after(chainedAssignment, parent), chainedAssignment, parent)
}
fun visit(ongoto: OnGoto, parent: Node) {
track(before(ongoto, parent), ongoto, parent)
ongoto.index.accept(this, ongoto)
ongoto.labels.forEach { it.accept(this, ongoto) }
ongoto.elsepart?.accept(this, ongoto)
track(after(ongoto, parent), ongoto, parent)
}
}

View File

@ -199,4 +199,10 @@ interface IAstVisitor {
chainedAssignment.target.accept(this)
chainedAssignment.nested.accept(this)
}
fun visit(onGoto: OnGoto) {
onGoto.index.accept(this)
onGoto.labels.forEach { it.accept(this) }
onGoto.elsepart?.accept(this)
}
}

View File

@ -2,9 +2,7 @@ package prog8.parser
import org.antlr.v4.runtime.*
import prog8.ast.Module
import prog8.ast.antlr.toAst
import prog8.ast.statements.Block
import prog8.ast.statements.Directive
import prog8.ast.antlr.Antlr2KotlinVisitor
import prog8.code.core.Position
import prog8.code.source.SourceCode
@ -25,41 +23,12 @@ object Prog8Parser {
parser.addErrorListener(antlrErrorListener)
val parseTree = parser.module()
val module = ParsedModule(src)
parseTree.module_element().forEach {
val block = it.block()?.toAst(module.isLibrary)
val directive = it.directive()?.toAst()
if(directive != null) module.add(directive)
if(block != null) module.add(block)
}
return module
val visitor = Antlr2KotlinVisitor(src)
val visitorResult = visitor.visit(parseTree)
return visitorResult as Module
}
private class ParsedModule(source: SourceCode) :
Module(mutableListOf(), Position(source.origin, 1, 0, 0), source)
{
/**
* Adds a [Directive] to [statements] and
* sets this Module as its [parent].
* Note: you can only add [Directive]s or [Block]s to a Module.
*/
fun add(child: Directive) {
child.linkParents(this)
statements.add(child)
}
/**
* Adds a [Block] to [statements] and
* sets this Module as its [parent].
* Note: you can only add [Directive]s or [Block]s to a Module.
*/
fun add(child: Block) {
child.linkParents(this)
statements.add(child)
}
}
private object Prog8ErrorStrategy: BailErrorStrategy() {
private fun fillIn(e: RecognitionException?, ctx: ParserRuleContext?) {

View File

@ -1,5 +1,5 @@
Prog8 compiler v11.3.1 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.4 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
@ -108,7 +108,6 @@ LIBRARY MODULE NAME: coroutines
coroutines {
const ubyte MAX_TASKS
ubyte active_task
uword[] returnaddresses
uword supervisor
uword[] tasklist
uword[] userdatas
@ -237,6 +236,7 @@ strings {
copy (uword source @R0, uword target @AY) -> clobbers (A) -> ubyte @Y
endswith (str st, str suffix) -> bool
find (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
find_eol (uword string @AY) -> ubyte @A, bool @Pc
findstr (str haystack, str needle) -> ubyte
hash (str string @AY) -> ubyte @A
isdigit (ubyte petsciichar @A) -> bool @Pc
@ -514,6 +514,7 @@ sys {
const ubyte SIZEOF_BOOL
const ubyte SIZEOF_BYTE
const ubyte SIZEOF_FLOAT
const ubyte SIZEOF_LONG
const ubyte SIZEOF_UBYTE
const ubyte SIZEOF_UWORD
const ubyte SIZEOF_WORD
@ -527,6 +528,7 @@ sys {
exit (ubyte returnvalue @A)
exit2 (ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y)
exit3 (ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc)
get_as_returnaddress (uword address @XY) -> uword @AX
internal_stringcopy (uword source @R0, uword target @AY) -> clobbers (A,Y)
irqsafe_clear_irqd ()
irqsafe_set_irqd ()
@ -559,6 +561,8 @@ cx16 {
&uword r0
&ubyte r0H
&ubyte r0L
&bool r0bH
&bool r0bL
&word r0s
&byte r0sH
&byte r0sL
@ -566,89 +570,119 @@ cx16 {
&uword r10
&ubyte r10H
&ubyte r10L
&bool r10bH
&bool r10bL
&word r10s
&byte r10sH
&byte r10sL
&uword r11
&ubyte r11H
&ubyte r11L
&bool r11bH
&bool r11bL
&word r11s
&byte r11sH
&byte r11sL
&uword r12
&ubyte r12H
&ubyte r12L
&bool r12bH
&bool r12bL
&word r12s
&byte r12sH
&byte r12sL
&uword r13
&ubyte r13H
&ubyte r13L
&bool r13bH
&bool r13bL
&word r13s
&byte r13sH
&byte r13sL
&uword r14
&ubyte r14H
&ubyte r14L
&bool r14bH
&bool r14bL
&word r14s
&byte r14sH
&byte r14sL
&uword r15
&ubyte r15H
&ubyte r15L
&bool r15bH
&bool r15bL
&word r15s
&byte r15sH
&byte r15sL
&ubyte r1H
&ubyte r1L
&bool r1bH
&bool r1bL
&word r1s
&byte r1sH
&byte r1sL
&uword r2
&ubyte r2H
&ubyte r2L
&bool r2bH
&bool r2bL
&word r2s
&byte r2sH
&byte r2sL
&uword r3
&ubyte r3H
&ubyte r3L
&bool r3bH
&bool r3bL
&word r3s
&byte r3sH
&byte r3sL
&uword r4
&ubyte r4H
&ubyte r4L
&bool r4bH
&bool r4bL
&word r4s
&byte r4sH
&byte r4sL
&uword r5
&ubyte r5H
&ubyte r5L
&bool r5bH
&bool r5bL
&word r5s
&byte r5sH
&byte r5sL
&uword r6
&ubyte r6H
&ubyte r6L
&bool r6bH
&bool r6bL
&word r6s
&byte r6sH
&byte r6sL
&uword r7
&ubyte r7H
&ubyte r7L
&bool r7bH
&bool r7bL
&word r7s
&byte r7sH
&byte r7sL
&uword r8
&ubyte r8H
&ubyte r8L
&bool r8bH
&bool r8bL
&word r8s
&byte r8sH
&byte r8sL
&uword r9
&ubyte r9H
&ubyte r9L
&bool r9bH
&bool r9bL
&word r9s
&byte r9sH
&byte r9sL

View File

@ -1,5 +1,5 @@
Prog8 compiler v11.3.1 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.4 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
@ -108,7 +108,6 @@ LIBRARY MODULE NAME: coroutines
coroutines {
const ubyte MAX_TASKS
ubyte active_task
uword[] returnaddresses
uword supervisor
uword[] tasklist
uword[] userdatas
@ -364,6 +363,7 @@ strings {
copy (uword source @R0, uword target @AY) -> clobbers (A) -> ubyte @Y
endswith (str st, str suffix) -> bool
find (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
find_eol (uword string @AY) -> ubyte @A, bool @Pc
findstr (str haystack, str needle) -> ubyte
hash (str string @AY) -> ubyte @A
isdigit (ubyte petsciichar @A) -> bool @Pc
@ -643,6 +643,7 @@ sys {
const ubyte SIZEOF_BOOL
const ubyte SIZEOF_BYTE
const ubyte SIZEOF_FLOAT
const ubyte SIZEOF_LONG
const ubyte SIZEOF_UBYTE
const ubyte SIZEOF_UWORD
const ubyte SIZEOF_WORD
@ -656,6 +657,7 @@ sys {
exit (ubyte returnvalue @A)
exit2 (ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y)
exit3 (ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc)
get_as_returnaddress (uword address @XY) -> uword @AX
internal_stringcopy (uword source @R0, uword target @AY) -> clobbers (A,Y)
irqsafe_clear_irqd ()
irqsafe_set_irqd ()
@ -688,6 +690,8 @@ cx16 {
&uword r0
&ubyte r0H
&ubyte r0L
&bool r0bH
&bool r0bL
&word r0s
&byte r0sH
&byte r0sL
@ -695,89 +699,119 @@ cx16 {
&uword r10
&ubyte r10H
&ubyte r10L
&bool r10bH
&bool r10bL
&word r10s
&byte r10sH
&byte r10sL
&uword r11
&ubyte r11H
&ubyte r11L
&bool r11bH
&bool r11bL
&word r11s
&byte r11sH
&byte r11sL
&uword r12
&ubyte r12H
&ubyte r12L
&bool r12bH
&bool r12bL
&word r12s
&byte r12sH
&byte r12sL
&uword r13
&ubyte r13H
&ubyte r13L
&bool r13bH
&bool r13bL
&word r13s
&byte r13sH
&byte r13sL
&uword r14
&ubyte r14H
&ubyte r14L
&bool r14bH
&bool r14bL
&word r14s
&byte r14sH
&byte r14sL
&uword r15
&ubyte r15H
&ubyte r15L
&bool r15bH
&bool r15bL
&word r15s
&byte r15sH
&byte r15sL
&ubyte r1H
&ubyte r1L
&bool r1bH
&bool r1bL
&word r1s
&byte r1sH
&byte r1sL
&uword r2
&ubyte r2H
&ubyte r2L
&bool r2bH
&bool r2bL
&word r2s
&byte r2sH
&byte r2sL
&uword r3
&ubyte r3H
&ubyte r3L
&bool r3bH
&bool r3bL
&word r3s
&byte r3sH
&byte r3sL
&uword r4
&ubyte r4H
&ubyte r4L
&bool r4bH
&bool r4bL
&word r4s
&byte r4sH
&byte r4sL
&uword r5
&ubyte r5H
&ubyte r5L
&bool r5bH
&bool r5bL
&word r5s
&byte r5sH
&byte r5sL
&uword r6
&ubyte r6H
&ubyte r6L
&bool r6bH
&bool r6bL
&word r6s
&byte r6sH
&byte r6sL
&uword r7
&ubyte r7H
&ubyte r7L
&bool r7bH
&bool r7bL
&word r7s
&byte r7sH
&byte r7sL
&uword r8
&ubyte r8H
&ubyte r8L
&bool r8bH
&bool r8bL
&word r8s
&byte r8sH
&byte r8sL
&uword r9
&ubyte r9H
&ubyte r9L
&bool r9bH
&bool r9bL
&word r9s
&byte r9sH
&byte r9sL

View File

@ -1,5 +1,5 @@
Prog8 compiler v11.3.1 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.4 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
@ -146,7 +146,6 @@ LIBRARY MODULE NAME: coroutines
coroutines {
const ubyte MAX_TASKS
ubyte active_task
uword[] returnaddresses
uword supervisor
uword[] tasklist
uword[] userdatas
@ -530,6 +529,7 @@ LIBRARY MODULE NAME: monogfx
----------------------------
monogfx {
uword buffer_visible, buffer_back
const ubyte MODE_INVERT
const ubyte MODE_NORMAL
const ubyte MODE_STIPPLE
@ -545,6 +545,7 @@ monogfx {
cs_innerloop640 (bool draw @A) -> clobbers (Y)
disc (uword xcenter, uword ycenter, ubyte radius, bool draw)
drawmode (ubyte dm)
enable_doublebuffer ()
fill (uword x, uword y, bool draw, ubyte stack_rambank)
fillrect (uword xx, uword yy, uword rwidth, uword rheight, bool draw)
hires ()
@ -560,6 +561,7 @@ monogfx {
safe_disc (uword xcenter, uword ycenter, ubyte radius, bool draw)
safe_horizontal_line (uword xx, uword yy, uword length, bool draw)
safe_plot (uword xx, uword yy, bool draw)
swap_buffers (bool wait_for_vsync)
text (uword xx, uword yy, bool draw, str sctextptr)
text_charset (ubyte charset)
textmode ()
@ -672,6 +674,7 @@ strings {
copy (uword source @R0, uword target @AY) -> clobbers (A) -> ubyte @Y
endswith (str st, str suffix) -> bool
find (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
find_eol (uword string @AY) -> ubyte @A, bool @Pc
findstr (str haystack, str needle) -> ubyte
hash (str string @AY) -> ubyte @A
isdigit (ubyte petsciichar @A) -> bool @Pc
@ -890,6 +893,8 @@ cx16 {
&uword r0
&ubyte r0H
&ubyte r0L
&bool r0bH
&bool r0bL
&word r0s
&byte r0sH
&byte r0sL
@ -897,89 +902,119 @@ cx16 {
&uword r10
&ubyte r10H
&ubyte r10L
&bool r10bH
&bool r10bL
&word r10s
&byte r10sH
&byte r10sL
&uword r11
&ubyte r11H
&ubyte r11L
&bool r11bH
&bool r11bL
&word r11s
&byte r11sH
&byte r11sL
&uword r12
&ubyte r12H
&ubyte r12L
&bool r12bH
&bool r12bL
&word r12s
&byte r12sH
&byte r12sL
&uword r13
&ubyte r13H
&ubyte r13L
&bool r13bH
&bool r13bL
&word r13s
&byte r13sH
&byte r13sL
&uword r14
&ubyte r14H
&ubyte r14L
&bool r14bH
&bool r14bL
&word r14s
&byte r14sH
&byte r14sL
&uword r15
&ubyte r15H
&ubyte r15L
&bool r15bH
&bool r15bL
&word r15s
&byte r15sH
&byte r15sL
&ubyte r1H
&ubyte r1L
&bool r1bH
&bool r1bL
&word r1s
&byte r1sH
&byte r1sL
&uword r2
&ubyte r2H
&ubyte r2L
&bool r2bH
&bool r2bL
&word r2s
&byte r2sH
&byte r2sL
&uword r3
&ubyte r3H
&ubyte r3L
&bool r3bH
&bool r3bL
&word r3s
&byte r3sH
&byte r3sL
&uword r4
&ubyte r4H
&ubyte r4L
&bool r4bH
&bool r4bL
&word r4s
&byte r4sH
&byte r4sL
&uword r5
&ubyte r5H
&ubyte r5L
&bool r5bH
&bool r5bL
&word r5s
&byte r5sH
&byte r5sL
&uword r6
&ubyte r6H
&ubyte r6L
&bool r6bH
&bool r6bL
&word r6s
&byte r6sH
&byte r6sL
&uword r7
&ubyte r7H
&ubyte r7L
&bool r7bH
&bool r7bL
&word r7s
&byte r7sH
&byte r7sL
&uword r8
&ubyte r8H
&ubyte r8L
&bool r8bH
&bool r8bL
&word r8s
&byte r8sH
&byte r8sL
&uword r9
&ubyte r9H
&ubyte r9L
&bool r9bH
&bool r9bL
&word r9s
&byte r9sH
&byte r9sL
@ -1093,6 +1128,8 @@ cx16 {
iso_cursor_char (ubyte character @X) -> clobbers (A,X,Y)
joystick_get (ubyte joynr @A) -> uword @AX, bool @Y = $ff56
joystick_scan () -> clobbers (A,X,Y) = $ff53
joysticks_detect () -> ubyte
joysticks_getall (bool also_keyboard_js) -> uword
kbdbuf_get_modifiers () -> ubyte @A = $fec0
kbdbuf_peek () -> ubyte @A, ubyte @X = $febd
kbdbuf_put (ubyte key @A) -> clobbers (X) = $fec3
@ -1215,6 +1252,7 @@ sys {
const ubyte SIZEOF_BOOL
const ubyte SIZEOF_BYTE
const ubyte SIZEOF_FLOAT
const ubyte SIZEOF_LONG
const ubyte SIZEOF_UBYTE
const ubyte SIZEOF_UWORD
const ubyte SIZEOF_WORD
@ -1226,6 +1264,7 @@ sys {
exit (ubyte returnvalue @A)
exit2 (ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y)
exit3 (ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc)
get_as_returnaddress (uword address @XY) -> uword @AX
internal_stringcopy (uword source @R0, uword target @AY) -> clobbers (A,Y)
irqsafe_clear_irqd ()
irqsafe_set_irqd ()

View File

@ -1,5 +1,5 @@
Prog8 compiler v11.3.1 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.4 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
@ -108,7 +108,6 @@ LIBRARY MODULE NAME: coroutines
coroutines {
const ubyte MAX_TASKS
ubyte active_task
uword[] returnaddresses
uword supervisor
uword[] tasklist
uword[] userdatas
@ -188,6 +187,7 @@ strings {
copy (uword source @R0, uword target @AY) -> clobbers (A) -> ubyte @Y
endswith (str st, str suffix) -> bool
find (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
find_eol (uword string @AY) -> ubyte @A, bool @Pc
findstr (str haystack, str needle) -> ubyte
hash (str string @AY) -> ubyte @A
isdigit (ubyte petsciichar @A) -> bool @Pc
@ -263,6 +263,7 @@ sys {
const ubyte SIZEOF_BOOL
const ubyte SIZEOF_BYTE
const ubyte SIZEOF_FLOAT
const ubyte SIZEOF_LONG
const ubyte SIZEOF_UBYTE
const ubyte SIZEOF_UWORD
const ubyte SIZEOF_WORD
@ -274,6 +275,7 @@ sys {
exit (ubyte returnvalue @A)
exit2 (ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y)
exit3 (ubyte resulta @A, ubyte resultx @X, ubyte resulty @Y, bool carry @Pc)
get_as_returnaddress (uword address @XY) -> uword @AX
internal_stringcopy (uword source @R0, uword target @AY) -> clobbers (A,Y)
irqsafe_clear_irqd ()
irqsafe_set_irqd ()
@ -302,6 +304,8 @@ cx16 {
&uword r0
&ubyte r0H
&ubyte r0L
&bool r0bH
&bool r0bL
&word r0s
&byte r0sH
&byte r0sL
@ -309,89 +313,119 @@ cx16 {
&uword r10
&ubyte r10H
&ubyte r10L
&bool r10bH
&bool r10bL
&word r10s
&byte r10sH
&byte r10sL
&uword r11
&ubyte r11H
&ubyte r11L
&bool r11bH
&bool r11bL
&word r11s
&byte r11sH
&byte r11sL
&uword r12
&ubyte r12H
&ubyte r12L
&bool r12bH
&bool r12bL
&word r12s
&byte r12sH
&byte r12sL
&uword r13
&ubyte r13H
&ubyte r13L
&bool r13bH
&bool r13bL
&word r13s
&byte r13sH
&byte r13sL
&uword r14
&ubyte r14H
&ubyte r14L
&bool r14bH
&bool r14bL
&word r14s
&byte r14sH
&byte r14sL
&uword r15
&ubyte r15H
&ubyte r15L
&bool r15bH
&bool r15bL
&word r15s
&byte r15sH
&byte r15sL
&ubyte r1H
&ubyte r1L
&bool r1bH
&bool r1bL
&word r1s
&byte r1sH
&byte r1sL
&uword r2
&ubyte r2H
&ubyte r2L
&bool r2bH
&bool r2bL
&word r2s
&byte r2sH
&byte r2sL
&uword r3
&ubyte r3H
&ubyte r3L
&bool r3bH
&bool r3bL
&word r3s
&byte r3sH
&byte r3sL
&uword r4
&ubyte r4H
&ubyte r4L
&bool r4bH
&bool r4bL
&word r4s
&byte r4sH
&byte r4sL
&uword r5
&ubyte r5H
&ubyte r5L
&bool r5bH
&bool r5bL
&word r5s
&byte r5sH
&byte r5sL
&uword r6
&ubyte r6H
&ubyte r6L
&bool r6bH
&bool r6bL
&word r6s
&byte r6sH
&byte r6sL
&uword r7
&ubyte r7H
&ubyte r7L
&bool r7bH
&bool r7bL
&word r7s
&byte r7sH
&byte r7sL
&uword r8
&ubyte r8H
&ubyte r8L
&bool r8bH
&bool r8bL
&word r8s
&byte r8sH
&byte r8sL
&uword r9
&ubyte r9H
&ubyte r9L
&bool r9bH
&bool r9bL
&word r9s
&byte r9sH
&byte r9sL

View File

@ -1,5 +1,5 @@
Prog8 compiler v11.3.1 by Irmen de Jong (irmen@razorvine.net)
Prog8 compiler v11.4 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
@ -279,7 +279,8 @@ strings {
contains (str st, ubyte character) -> bool
copy (str source, str target) -> ubyte
endswith (str st, str suffix) -> bool
find (str st, ubyte character) -> ubyte
find (str st, ubyte character) -> ubyte, bool
find_eol (str st) -> ubyte, bool
findstr (str haystack, str needle) -> ubyte
hash (str st) -> ubyte
isdigit (ubyte character) -> bool
@ -297,7 +298,7 @@ strings {
ltrim (str s)
ltrimmed (str s) -> str
ncompare (str st1, str st2, ubyte length) -> byte
rfind (uword stringptr, ubyte character) -> ubyte
rfind (uword stringptr, ubyte character) -> ubyte, bool
right (str source, ubyte slen, str target)
rstrip (str s)
rtrim (str s)
@ -327,6 +328,7 @@ sys {
const ubyte SIZEOF_BOOL
const ubyte SIZEOF_BYTE
const ubyte SIZEOF_FLOAT
const ubyte SIZEOF_LONG
const ubyte SIZEOF_UBYTE
const ubyte SIZEOF_UWORD
const ubyte SIZEOF_WORD
@ -336,6 +338,7 @@ sys {
disable_caseswitch ()
enable_caseswitch ()
exit (ubyte returnvalue)
get_as_returnaddress (uword address) -> uword
gfx_clear (ubyte color)
gfx_enable (ubyte mode)
gfx_getpixel (uword xx, uword yy) -> ubyte
@ -364,6 +367,8 @@ cx16 {
&uword r0
&ubyte r0H
&ubyte r0L
&bool r0bH
&bool r0bL
&word r0s
&byte r0sH
&byte r0sL
@ -371,89 +376,119 @@ cx16 {
&uword r10
&ubyte r10H
&ubyte r10L
&bool r10bH
&bool r10bL
&word r10s
&byte r10sH
&byte r10sL
&uword r11
&ubyte r11H
&ubyte r11L
&bool r11bH
&bool r11bL
&word r11s
&byte r11sH
&byte r11sL
&uword r12
&ubyte r12H
&ubyte r12L
&bool r12bH
&bool r12bL
&word r12s
&byte r12sH
&byte r12sL
&uword r13
&ubyte r13H
&ubyte r13L
&bool r13bH
&bool r13bL
&word r13s
&byte r13sH
&byte r13sL
&uword r14
&ubyte r14H
&ubyte r14L
&bool r14bH
&bool r14bL
&word r14s
&byte r14sH
&byte r14sL
&uword r15
&ubyte r15H
&ubyte r15L
&bool r15bH
&bool r15bL
&word r15s
&byte r15sH
&byte r15sL
&ubyte r1H
&ubyte r1L
&bool r1bH
&bool r1bL
&word r1s
&byte r1sH
&byte r1sL
&uword r2
&ubyte r2H
&ubyte r2L
&bool r2bH
&bool r2bL
&word r2s
&byte r2sH
&byte r2sL
&uword r3
&ubyte r3H
&ubyte r3L
&bool r3bH
&bool r3bL
&word r3s
&byte r3sH
&byte r3sL
&uword r4
&ubyte r4H
&ubyte r4L
&bool r4bH
&bool r4bL
&word r4s
&byte r4sH
&byte r4sL
&uword r5
&ubyte r5H
&ubyte r5L
&bool r5bH
&bool r5bL
&word r5s
&byte r5sH
&byte r5sL
&uword r6
&ubyte r6H
&ubyte r6L
&bool r6bH
&bool r6bL
&word r6s
&byte r6sH
&byte r6sL
&uword r7
&ubyte r7H
&ubyte r7L
&bool r7bH
&bool r7bL
&word r7s
&byte r7sH
&byte r7sL
&uword r8
&ubyte r8H
&ubyte r8L
&bool r8bH
&bool r8bL
&word r8s
&byte r8sH
&byte r8sL
&uword r9
&ubyte r9H
&ubyte r9L
&bool r9bH
&bool r9bL
&word r9s
&byte r9sH
&byte r9sL

View File

@ -18,14 +18,20 @@ Then you can choose a few ways to get a compiler:
**Or, install via a Package Manager:**
Currently, it's only available on `AUR <https://wiki.archlinux.org/title/Arch_User_Repository>`_ for Arch Linux and compatible systems.
The package is called `"prog8" <https://aur.archlinux.org/packages/prog8>`_.
Arch Linux:
Currently, it's available on `AUR <https://wiki.archlinux.org/title/Arch_User_Repository>`_ for Arch Linux and compatible systems.
The package is called `"prog8" <https://aur.archlinux.org/packages/prog8>`_.
This package, alongside the compiler itself, also globally installs syntax highlighting for ``vim`` and ``nano``.
In order to run compiler, you can type ``prog8c``. The usage of those commands is exactly the same as with the ``java -jar`` method.
This package, alongside the compiler itself, also globally installs syntax highlighting for ``vim`` and ``nano``.
In order to run compiler, you can type ``prog8c``. The usage of those commands is exactly the same as with the ``java -jar`` method.
In case you prefer to install AUR packages in a traditional manner, make sure to install `"tass64" package <https://aur.archlinux.org/packages/tass64>`_
before installing prog8, as `makepkg <https://wiki.archlinux.org/title/Makepkg>`_ itself doesn't fetch AUR dependencies.
In case you prefer to install AUR packages in a traditional manner, make sure to install `"tass64" package <https://aur.archlinux.org/packages/tass64>`_
before installing prog8, as `makepkg <https://wiki.archlinux.org/title/Makepkg>`_ itself doesn't fetch AUR dependencies.
Mac OS (and Linux, and WSL2 on Windows):
Prog8 can be installed via `Homebrew <https://formulae.brew.sh/formula/prog8>`_ using the command ``brew install prog8``.
It will make the ``prog8c`` command available and also installs the other required software tools for you.
While Homebrew works on Linux, it's probably best to first check your distribution's native package manager.
**Or, download a bleeding edge development version from Github:**
@ -236,6 +242,9 @@ One or more .p8 module files
machine's configuration and properties from that configuration file instead of using one of the built-in targets.
See :ref:`customizable_target` for details about this.
``-timings``
Show a more detailed breakdown of the time taken in various compiler phases, for performance analysis of the compiler itself.
``-varsgolden``
Like ``-varshigh``, but places the variables in the $0400-$07FF "golden ram" area instead.
Because this is in normal system memory, there are no bank switching issues.

View File

@ -101,6 +101,7 @@ Features
- Programs can be configured to execute in ROM
- Conditional branches for status flags that map 1:1 to processor branch instructions for optimal efficiency
- ``when`` statement to avoid if-else chains
- ``on .. goto`` statement for fast jump tables
- ``in`` expression for concise and efficient multi-value/containment test
- ``defer`` statement to help write concise and robust subroutine cleanup logic
- Several specialized built-in functions, such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``

View File

@ -178,10 +178,9 @@ setlsb (x, value)
setmsb (x, value)
Sets the most significant byte of word variable x to a new value. Leaves the LSB untouched.
sizeof (name) ; sizeof (number)
Number of bytes that the object 'name', or the number 'number' occupies in memory.
This is a constant determined by the data type of
the object. For instance, for a variable of type uword, the sizeof is 2.
sizeof (name) ; sizeof (number) ; sizeof(datatype)
The constant number of bytes that the object 'name', the number 'number' or the type 'datatype' occupies in memory.
For instance, for a variable of type uword, the sizeof is 2.
For an 10 element array of floats, it is 50 (on the C64, where a float is 5 bytes).
Note: usually you will be interested in the number of elements in an array, use len() for that.
@ -363,15 +362,13 @@ Read the `conv source code <https://github.com/irmen/prog8/tree/master/compiler/
to see what's in there.
coroutines (experimental)
-------------------------
coroutines
----------
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)::
@ -796,16 +793,17 @@ but perhaps the provided ones can be of service too.
``lerp(v0, v1, t)``
Linear interpolation routine for unsigned byte values.
Returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 255]
Guarantees v = v1 when t = 255.
Guarantees v = v1 when t = 255. Also works if v0 > v1.
``lerpw(v0, v1, t)``
Linear interpolation routine for unsigned word values.
Returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 65535]
Guarantees v = v1 when t = 65535.
Guarantees v = v1 when t = 65535. Also works if v0 > v1.
Clobbers R15.
``interpolate(v, inputMin, inputMax, outputMin, outputMax)``
Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax]
All values are unsigned bytes.
All values are unsigned bytes. Clobbers R15
(there is no version for word values because of lack of precision in the fixed point calculation there).
@ -816,6 +814,7 @@ Full-screen lores or hires monochrome bitmap graphics routines, available on the
- two resolutions: lores 320*240 or hires 640*480 bitmap mode
- optimized routines for monochrome (2-color) graphics
- clearing screen, switching screen mode, also back to text mode
- doublebuffering option to avoid flicker
- drawing and reading individual pixels
- drawing lines, rectangles, filled rectangles, circles, discs
- flood fill
@ -823,7 +822,7 @@ Full-screen lores or hires monochrome bitmap graphics routines, available on the
- can draw using a stipple pattern (alternate black/white pixels) and in invert mode (toggle pixels)
Read the `monogfx source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/cx16/monogfx.p8>`_
to see what's in there.
and the `testmonogfx` example program, to see what's in there.
palette (cx16 only)
@ -1177,9 +1176,7 @@ to see what's in there. (Note: slight variations for different compiler targets)
verafx (cx16 only)
-------------------
Available for the Cx16 target.
Experimental routines that use the new Vera FX logic (hopefully coming in the Vera in new X16 boards,
the emulators already support it).
Available for the Cx16 target. Routines that use the Vera FX logic to accelerate certain operations.
``available``
Returns true if Vera FX is available, false if not (that would be an older Vera chip)

View File

@ -24,7 +24,6 @@ Comments
Everything on the line after a semicolon ``;`` is a comment and is ignored by the compiler.
If the whole line is just a comment, this line will be copied into the resulting assembly source code for reference.
There's also a block-comment: everything surrounded with ``/*`` and ``*/`` is ignored and this can span multiple lines.
This block comment is experimental for now: it may change or even be removed again in a future compiler version.
The recommended way to comment out a bunch of lines remains to just bulk comment them individually with ``;``.
Directive
@ -744,9 +743,30 @@ An example, to select the number of cards to use depending on what game is playe
numcards = 52
on .. goto / on .. call statement (jump table)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
when statement ('jump table')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``on goto / call`` statement is suitable to create a fast call of a subroutine from a list based on an index value.
it selects a function to jump to in O(1) whereas a similar when-statement, runs in O(n) because that one checks each index value.
The ``on goto / call`` instead simply gets the correct entry from an array of function pointers and jumps to it directly.
The index value that is used is 0-based; 0 will jump to the first entry in the list, 1 to the second, and so on.
If the value is too large (i.e. outside the list of functions), no call is performed, and execution continues.
In this case you can optionally add an ``else`` block that is then executed instead. Here's an example::
on math.randrange(5) call (
routines.func1,
routines.func2,
routines.func3 )
else {
txt.print("no call was done")
}
on math.randrange(5) goto (routines.func1, routines.func2, routines.func3)
txt.print("no jump was taken")
when statement
^^^^^^^^^^^^^^
Instead of writing a bunch of sequential if-elseif statements, it is more readable to
use a ``when`` statement. (It will also result in greatly improved assembly code generation)
@ -767,7 +787,8 @@ datatype as the when-value, otherwise no efficient comparison can be done.
You can explicitly put a list of numbers that all should result in the same case,
or even use any *range expression* as long as it denotes a constant list of numbers.
Be aware that every number is compared individually so using long lists of numbers and/or
many choice cases will result in poor performance.
many choice cases will result in poor performance. If you need to call a certain function
based on some sequential index number, look at the ``on .. goto / call`` statement instead.
Choices can result in a single statement or a block of multiple statements in which
case you have to use { } to enclose them.
@ -1092,14 +1113,19 @@ so pay attention to any jumps and rts instructions in the inlined code!
don't want a ``rts`` or ``jmp`` or ``bra`` in it!
.. note::
The 'virtual' 16-bit registers from the Commander X16 can also be specified as ``R0`` .. ``R15`` .
The **sixteen 'virtual' 16-bit registers** from the Commander X16 can also be specified as ``R0`` .. ``R15`` .
This means you don't have to set them up manually before calling a subroutine that takes
one or more parameters in those 'registers'. You can just list the arguments directly.
*This also works on the Commodore 64!* (however they are not as efficient there because they're not in zeropage)
In prog8 and assembly code these 'registers' are directly accessible too via
``cx16.r0`` .. ``cx16.r15`` (these are memory-mapped uword values),
``cx16.r0s`` .. ``cx16.r15s`` (these are memory-mapped word values),
and ``L`` / ``H`` variants are also available to directly access the low and high bytes of these.
*This also works on the other compilation targets!* (however they might not be as efficient there as on the X16,
because on most other targets such as the C64, these registers are not placed in zeropage due to lack of space)
In both regular **prog8** *and* **assembly** code these 'registers' are directly accessible too via:
- ``cx16.r0`` - ``cx16.r15`` (memory-mapped **uword** values, most often these are used)
- ``cx16.r0s`` - ``cx16.r15s`` (memory-mapped **word** values, used when you need a signed word)
- ``cx16.r0H``, ``cx16.r0L`` (for each r0..r15; memory-mapped **ubyte** values, both bytes of the register)
- ``cx16.r0sH``, ``cx16.r0sL`` (for each r0..r15; memory-mapped **byte** values, both bytes of the register)
- ``cx16.r0bH``, ``cx16.r0bL`` (for each r0..r15; memory-mapped **bool** values, both bytes of the register)
You can use them directly but their name isn't very descriptive, so it may be useful to define
an alias for them when using them regularly.

View File

@ -1,18 +1,17 @@
TODO
====
STRUCTS: are being developed in their own separate branch for now, called "structs".
Idea is to make it feature complete in the IR/Virtual target, then merge it to master?, and then start building the 6502 code generation for it.
...
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
- for version 12.0 (branch pre12) : translate '\n' to char code 13 in ISO encoding as well (and all others that don't do it yet, EXCEPT cp437, because it has no control chars) so you don't have to use \r anymore as newline
- for version 12.0 (branch pre12) : on <expr> goto label1, label2, label3 and 'call' instead of 'goto' to make it a JSR. Faster jump tables than a when, and much easier to write than a call + an array. Number starts at 0. If number too large, no jump is taken. (or add 'else failLabel' ?) else @dirty to skip all checks?
- STRUCTS: are now being developed in their own separate branch "structs". This will be for the next major version of the compiler (v12)
- is "checkAssignmentCompatible" redundant (gets called just 1 time!) when we also have "checkValueTypeAndRange" ?
- enums?
- romable: should we have a way to explicitly set the memory address for the BSS area (instead of only the highram bank number on X16, allow a memory address too for the -varshigh option?)
- romable: should we have a way to explicitly set the memory address for the BSS area (add a -varsaddress and -slabsaddress options?)
- romable: fix remaining codegens (some for loops, see ForLoopsAsmGen)
- Kotlin: can we use inline value classes in certain spots?
- add float support to the configurable compiler targets
- 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
@ -24,7 +23,7 @@ Future Things and Ideas
Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEvaluating() and argumentsViaRegisters().
- Does it make codegen easier if everything is an expression? Start with the PtProgram ast classes, change statements to expressions that have (new) VOID data type
- Can we support signed % (remainder) somehow?
- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays. Probaby only useful if we have typed pointers. (addressed in 'struct' branch)
- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays. Probaby only useful once we have typed pointers. (addressed in 'struct' branch)
- make a form of "manual generics" possible like: varsub routine(T arg)->T where T is expanded to a specific type
(this is already done hardcoded for several of the builtin functions)
- [much work:] more support for (64tass) SEGMENTS in the prog8 syntax itself?
@ -60,7 +59,6 @@ IR/VM
Libraries
---------
- Add split-word array sorting routines to sorting module?
- cx16: _irq_dispatcher now only dispatches a single irq source, better to ROL/BCC to handle *all* possible (multiple) sources.
- See if the raster interrupt handler on the C64 can be tweaked to be a more stable raster irq
- pet32 target: make syslib more complete (missing kernal routines)?
- need help with: PET disk routines (OPEN, SETLFS etc are not exposed as kernal calls)
@ -70,12 +68,10 @@ Libraries
Optimizations
-------------
- when choices that end with a goto create a redundant bra, get rid of it. Also some redundant RTS somewhere?
- Sorting module gnomesort_uw could be optimized more by fully rewriting it in asm? Shellshort seems consistently faster even if most of the words are already sorted.
- can the for loop temp var be replaced by the same logic that createRepeatCounterVar() uses for repeat loops? Take care of nested for/repeat loops to not use the same var
- in Identifier: use typedarray of strings instead of listOf? Other places?
- Compilation speed: try to join multiple modifications in 1 result in the AST processors instead of returning it straight away every time
- Compare output of some Oscar64 samples to what prog8 does for the equivalent code (see https://github.com/drmortalwombat/OscarTutorials/tree/main and https://github.com/drmortalwombat/oscar64/tree/main/samples)
- Optimize the IfExpression code generation to be more like regular if-else code. (both 6502 and IR) search for "TODO don't store condition as expression"
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served?
for instance, vars used inside loops first, then loopvars, then uwords used as pointers (or these first??), then the rest
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once 6502-codegen is done from IR code,
those checks should probably be removed, or be made permanent
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once 6502-codegen is done from IR code, those checks should probably be removed, or be made permanent

View File

@ -23,12 +23,13 @@ sys {
const ubyte target = 8 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_FLOAT = 0 ; undefined, no float support
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_FLOAT = 0 ; undefined, no floats supported
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
const ubyte MIN_UBYTE = 0
@ -383,6 +384,18 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
@ -402,7 +415,6 @@ save_SCRATCH_ZPWORD2 .word ?
cx16 {
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
; they are simulated on the Atari as well but their location in memory is different
; TODO
&uword r0 = $1b00
&uword r1 = $1b02
&uword r2 = $1b04
@ -420,6 +432,7 @@ cx16 {
&uword r14 = $1b1c
&uword r15 = $1b1e
; signed word versions
&word r0s = $1b00
&word r1s = $1b02
&word r2s = $1b04
@ -437,6 +450,7 @@ cx16 {
&word r14s = $1b1c
&word r15s = $1b1e
; ubyte versions (low and high bytes)
&ubyte r0L = $1b00
&ubyte r1L = $1b02
&ubyte r2L = $1b04
@ -471,6 +485,7 @@ cx16 {
&ubyte r14H = $1b1d
&ubyte r15H = $1b1f
; signed byte versions (low and high bytes)
&byte r0sL = $1b00
&byte r1sL = $1b02
&byte r2sL = $1b04
@ -505,6 +520,41 @@ cx16 {
&byte r14sH = $1b1d
&byte r15sH = $1b1f
; boolean versions
&bool r0bL = $1b00
&bool r1bL = $1b02
&bool r2bL = $1b04
&bool r3bL = $1b06
&bool r4bL = $1b08
&bool r5bL = $1b0a
&bool r6bL = $1b0c
&bool r7bL = $1b0e
&bool r8bL = $1b10
&bool r9bL = $1b12
&bool r10bL = $1b14
&bool r11bL = $1b16
&bool r12bL = $1b18
&bool r13bL = $1b1a
&bool r14bL = $1b1c
&bool r15bL = $1b1e
&bool r0bH = $1b01
&bool r1bH = $1b03
&bool r2bH = $1b05
&bool r3bH = $1b07
&bool r4bH = $1b09
&bool r5bH = $1b0b
&bool r6bH = $1b0d
&bool r7bH = $1b0f
&bool r8bH = $1b11
&bool r9bH = $1b13
&bool r10bH = $1b15
&bool r11bH = $1b17
&bool r12bH = $1b19
&bool r13bH = $1b1b
&bool r14bH = $1b1d
&bool r15bH = $1b1f
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31

View File

@ -15,11 +15,12 @@ sys {
const ubyte target = 7 ; compilation target specifier. 255=virtual, 128=C128, 64=C64, 32=PET, 16=CommanderX16, 8=atari800XL, 7=Neo6502
const ubyte SIZEOF_BOOL = 1
const ubyte SIZEOF_BYTE = 1
const ubyte SIZEOF_UBYTE = 1
const ubyte SIZEOF_WORD = 2
const ubyte SIZEOF_UWORD = 2
const ubyte SIZEOF_BOOL = sizeof(bool)
const ubyte SIZEOF_BYTE = sizeof(byte)
const ubyte SIZEOF_UBYTE = sizeof(ubyte)
const ubyte SIZEOF_WORD = sizeof(word)
const ubyte SIZEOF_UWORD = sizeof(uword)
const ubyte SIZEOF_LONG = sizeof(long)
const ubyte SIZEOF_FLOAT = 0 ; undefined, no floats supported
const byte MIN_BYTE = -128
const byte MAX_BYTE = 127
@ -316,6 +317,18 @@ save_SCRATCH_ZPWORD2 .word ?
}}
}
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
%asm {{
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
cpx #0
bne +
dey
+ dex
tya
rts
}}
}
inline asmsub pop() -> ubyte @A {
%asm {{
pla
@ -351,6 +364,7 @@ cx16 {
&uword r14 = $001e
&uword r15 = $0020
; signed word versions
&word r0s = $0002
&word r1s = $0004
&word r2s = $0006
@ -368,6 +382,7 @@ cx16 {
&word r14s = $001e
&word r15s = $0020
; ubyte versions (low and high bytes)
&ubyte r0L = $0002
&ubyte r1L = $0004
&ubyte r2L = $0006
@ -402,6 +417,7 @@ cx16 {
&ubyte r14H = $001f
&ubyte r15H = $0021
; signed byte versions (low and high bytes)
&byte r0sL = $0002
&byte r1sL = $0004
&byte r2sL = $0006
@ -436,6 +452,42 @@ cx16 {
&byte r14sH = $001f
&byte r15sH = $0021
; boolean versions
&bool r0bL = $0002
&bool r1bL = $0004
&bool r2bL = $0006
&bool r3bL = $0008
&bool r4bL = $000a
&bool r5bL = $000c
&bool r6bL = $000e
&bool r7bL = $0010
&bool r8bL = $0012
&bool r9bL = $0014
&bool r10bL = $0016
&bool r11bL = $0018
&bool r12bL = $001a
&bool r13bL = $001c
&bool r14bL = $001e
&bool r15bL = $0020
&bool r0bH = $0003
&bool r1bH = $0005
&bool r2bH = $0007
&bool r3bH = $0009
&bool r4bH = $000b
&bool r5bH = $000d
&bool r6bH = $000f
&bool r7bH = $0011
&bool r8bH = $0013
&bool r9bH = $0015
&bool r10bH = $0017
&bool r11bH = $0019
&bool r12bH = $001b
&bool r13bH = $001d
&bool r14bH = $001f
&bool r15bH = $0021
asmsub save_virtual_registers() clobbers(A,Y) {
%asm {{
ldy #31

View File

@ -159,6 +159,7 @@ cx16 {
&uword r14 = $cffc
&uword r15 = $cffe
; signed word versions
&word r0s = $cfe0
&word r1s = $cfe2
&word r2s = $cfe4
@ -176,6 +177,7 @@ cx16 {
&word r14s = $cffc
&word r15s = $cffe
; ubyte versions (low and high bytes)
&ubyte r0L = $cfe0
&ubyte r1L = $cfe2
&ubyte r2L = $cfe4
@ -210,6 +212,7 @@ cx16 {
&ubyte r14H = $cffd
&ubyte r15H = $cfff
; signed byte versions (low and high bytes)
&byte r0sL = $cfe0
&byte r1sL = $cfe2
&byte r2sL = $cfe4
@ -243,4 +246,39 @@ cx16 {
&byte r13sH = $cffb
&byte r14sH = $cffd
&byte r15sH = $cfff
; boolean versions
&bool r0bL = $cfe0
&bool r1bL = $cfe2
&bool r2bL = $cfe4
&bool r3bL = $cfe6
&bool r4bL = $cfe8
&bool r5bL = $cfea
&bool r6bL = $cfec
&bool r7bL = $cfee
&bool r8bL = $cff0
&bool r9bL = $cff2
&bool r10bL = $cff4
&bool r11bL = $cff6
&bool r12bL = $cff8
&bool r13bL = $cffa
&bool r14bL = $cffc
&bool r15bL = $cffe
&bool r0bH = $cfe1
&bool r1bH = $cfe3
&bool r2bH = $cfe5
&bool r3bH = $cfe7
&bool r4bH = $cfe9
&bool r5bH = $cfeb
&bool r6bH = $cfed
&bool r7bH = $cfef
&bool r8bH = $cff1
&bool r9bH = $cff3
&bool r10bH = $cff5
&bool r11bH = $cff7
&bool r12bH = $cff9
&bool r13bH = $cffb
&bool r14bH = $cffd
&bool r15bH = $cfff
}

View File

@ -159,6 +159,7 @@ cx16 {
&uword r14 = $001e
&uword r15 = $0020
; signed word versions
&word r0s = $0002
&word r1s = $0004
&word r2s = $0006
@ -176,6 +177,7 @@ cx16 {
&word r14s = $001e
&word r15s = $0020
; ubyte versions (low and high bytes)
&ubyte r0L = $0002
&ubyte r1L = $0004
&ubyte r2L = $0006
@ -210,6 +212,7 @@ cx16 {
&ubyte r14H = $001f
&ubyte r15H = $0021
; signed byte versions (low and high bytes)
&byte r0sL = $0002
&byte r1sL = $0004
&byte r2sL = $0006
@ -243,4 +246,39 @@ cx16 {
&byte r13sH = $001d
&byte r14sH = $001f
&byte r15sH = $0021
; boolean versions
&bool r0bL = $0002
&bool r1bL = $0004
&bool r2bL = $0006
&bool r3bL = $0008
&bool r4bL = $000a
&bool r5bL = $000c
&bool r6bL = $000e
&bool r7bL = $0010
&bool r8bL = $0012
&bool r9bL = $0014
&bool r10bL = $0016
&bool r11bL = $0018
&bool r12bL = $001a
&bool r13bL = $001c
&bool r14bL = $001e
&bool r15bL = $0020
&bool r0bH = $0003
&bool r1bH = $0005
&bool r2bH = $0007
&bool r3bH = $0009
&bool r4bH = $000b
&bool r5bH = $000d
&bool r6bH = $000f
&bool r7bH = $0011
&bool r8bH = $0013
&bool r9bH = $0015
&bool r10bH = $0017
&bool r11bH = $0019
&bool r12bH = $001b
&bool r13bH = $001d
&bool r14bH = $001f
&bool r15bH = $0021
}

Some files were not shown because too many files have changed in this diff Show More