Compare commits

...
86 Commits
Author SHA1 Message Date
Irmen de Jong 1f346230e3 release 7.4 2021-11-30 22:50:12 +01:00
Irmen de Jong a2860a7c8c todo 2021-11-30 22:45:43 +01:00
Irmen de Jong df997e5d3b don't write the asm file twice 2021-11-30 03:47:57 +01:00
Irmen de Jong a67a82c921 tweak 2021-11-30 03:05:25 +01:00
Irmen de Jong ea0fe8d3d2 PrefixExpression doesn't cause clobber risk 2021-11-30 02:32:37 +01:00
Irmen de Jong 2560042ac7 fix compiler crashes on in-place operations on cx16 registers or invalid signed types 2021-11-30 02:27:37 +01:00
Irmen de Jong 3d1d0696b9 refactor compiler arguments passing 2021-11-30 01:40:21 +01:00
Irmen de Jong 83f893f50b doc 2021-11-30 00:54:03 +01:00
Irmen de Jong 9ecf95b075 fix syntaxerror in const processing of ranges if it contained variables 2021-11-29 23:36:41 +01:00
Irmen de Jong 7748c261da rsave/rrestore moved from sys to builtin function to solve the stack related problem when calling it as a regular subroutine 2021-11-29 23:13:04 +01:00
Irmen de Jong a2db44f80c also consider Y register for clobber check for functioncall arguments 2021-11-29 22:09:05 +01:00
Irmen de Jong b438d8aec0 fix invalid range size check when stepval is not a positive integer 2021-11-29 02:01:19 +01:00
Irmen de Jong 4ac169b210 formatting 2021-11-29 01:25:21 +01:00
Irmen de Jong 56dc6d7f1e comment 2021-11-29 01:10:11 +01:00
Irmen de Jong 45b8762188 use inc/ina instead of adc 2021-11-29 00:07:15 +01:00
Irmen de Jong cafab98d10 correction 2021-11-28 18:59:36 +01:00
Irmen de Jong 9256f910f0 rollback binexpr splitting, caused slowdowns 2021-11-28 18:50:05 +01:00
Irmen de Jong 32068a832a split some additional binary expressions to avoid stack-based evaluation 2021-11-28 18:27:28 +01:00
Irmen de Jong 47c2c0376a added some cpu stack related assembly-level optimizations 2021-11-28 17:27:01 +01:00
Irmen de Jong f0dadc4a43 optimize 1-arg functioncalls 2021-11-28 16:55:10 +01:00
Irmen de Jong 960b60cd2d tweak 2021-11-28 14:06:12 +01:00
Irmen de Jong d6abd72e55 fix push() of signed values 2021-11-28 13:01:46 +01:00
Irmen de Jong 0a568f2530 fix the check of double-defined subroutine variables 2021-11-28 12:52:32 +01:00
Irmen de Jong c52aa648c0 use an AnonymousScope to contain GoSub changes instead of adding separate statements 2021-11-28 12:09:13 +01:00
Irmen de Jong 3d23b39f4c moved A to the end of the param list to avoid having to store its value 2021-11-28 04:03:18 +01:00
Irmen de Jong f3a4048ebf improved setting Carry bit as asmsub parameter 2021-11-28 03:31:32 +01:00
Irmen de Jong 1b07637cc4 better error checking for wrong pop() 2021-11-28 02:49:18 +01:00
Irmen de Jong 68b75fd558 fix: also allow pass-by-reference arguments to builtin functions that accept UWORD (adds implicit type cast) 2021-11-28 02:34:53 +01:00
Irmen de Jong 7c5ec1853d nice error message if pop() argument is wrong 2021-11-28 02:20:35 +01:00
Irmen de Jong e8f4686430 undid failed attempt of using sys.push/sys.pop for stack args - now using new push(), pushw(), pop(), popw() builtin functions 2021-11-28 01:22:40 +01:00
Irmen de Jong 02348924d0 failed attempt of using sys.push/pop for stack args 2021-11-27 23:52:47 +01:00
Irmen de Jong 69dcb4dbda fix reporting of (not) unused code after GoSub jump 2021-11-27 21:22:34 +01:00
Irmen de Jong c838821615 refactor fuction arguments codegen a bit 2021-11-27 21:14:21 +01:00
Irmen de Jong 8b4ac7801f fix sys.push() signature for c64 2021-11-27 20:18:41 +01:00
Irmen de Jong 64a411628d doc fixes 2021-11-27 19:58:08 +01:00
Irmen de Jong e8e25c6fd6 added sys.push() and sys.pop() to put values on cpu stack. Added missing builtin functions to syntax-files. 2021-11-27 18:09:15 +01:00
Irmen de Jong 62485b6851 allow assigns to asmsub parameters (registers), but this is not very useful in practice. 2021-11-27 15:41:44 +01:00
Irmen de Jong 54025d2bf5 small refactor and spelling fixes 2021-11-27 14:49:18 +01:00
Irmen de Jong f5ebf79e71 make sure X register is also saved if needed when GoSub is used 2021-11-26 22:11:52 +01:00
Irmen de Jong 66d5490702 just added missing FAC2 assign possibility 2021-11-26 21:34:00 +01:00
Irmen de Jong 42fe052f9f got rid of old getScopedSymbolNameForTarget routine 2021-11-26 21:09:29 +01:00
Irmen de Jong 58d9c46a9b got rid of old makeScopedName routine 2021-11-26 20:56:30 +01:00
Irmen de Jong e4648e2138 proper rounding of builtin functions that return int from float 2021-11-26 20:32:12 +01:00
Irmen de Jong 110e047681 replace subroutine calls (statement) by GoSub 2021-11-26 19:47:01 +01:00
Irmen de Jong 17d403d812 Merge branch 'ref-subroutine-param' into v7.4-dev
# Conflicts:
#	compilerAst/src/prog8/ast/AstToplevel.kt
2021-11-26 01:12:14 +01:00
Irmen de Jong 0a53bd4956 fix parameter name conflict 2021-11-26 01:01:59 +01:00
Irmen de Jong e52d05c7db fix some scoping related symbol lookup issues, clarified scoping rules in docs 2021-11-23 23:43:23 +01:00
Irmen de Jong b00db4f8a2 no longer report unknown type errors as well for unknown symbols,
added a bunch more unit tests for symbol scoping rules
2021-11-23 22:45:57 +01:00
Irmen de Jong 0c2f30fd45 links to 6502 bresenham line algorithms 2021-11-23 21:51:18 +01:00
Irmen de Jong e08871c637 oops! replace phx/plx 65C02 (cx16) instructions by 6502 (c64) compatible alternative.
Couldn't assemble code that used some of the routines in conv on c64 before...
2021-11-22 21:02:43 +01:00
Irmen de Jong ff715881bc allow scoped identifiers to reference a subroutine parameter directly.
also for asmsubroutines, but the asm generation for that is not yet done.
2021-11-21 23:21:39 +01:00
Irmen de Jong 0e2e5ffa52 fix parameter name conflict 2021-11-21 22:12:35 +01:00
Irmen de Jong 8095c4c155 added GoSub node (internal use only later for calling subroutines) 2021-11-21 16:23:48 +01:00
Irmen de Jong e86246a985 todo 2021-11-21 14:00:19 +01:00
Irmen de Jong 625aaa02eb documented the compiler's command line options in more detail 2021-11-21 13:53:22 +01:00
Irmen de Jong 787e35c9f3 asm optimizer can now also see of a symbol reference if it is in IO space or not (to a certain extent), so that these instructions are no longer optimized away 2021-11-21 13:12:51 +01:00
Irmen de Jong 8887e6af91 fix substituting 0 only if its actually the same variable that's substituted 2021-11-21 12:34:57 +01:00
Irmen de Jong dde4c751da version 7.4-dev 2021-11-21 03:28:13 +01:00
Irmen de Jong 3c39baf1d6 don't optimize seemingly redundant assembly instructions away that manipulate IO memory space 2021-11-21 03:24:03 +01:00
Irmen de Jong b292124f3c replaced many short/int values by unsigned types if appropriate 2021-11-21 00:55:56 +01:00
Irmen de Jong c0035ba1a2 char encodings now use UByte type instead of short 2021-11-21 00:07:17 +01:00
Irmen de Jong 2491509c6a add assignment optimization X=value-X --> X=-X ; X+=value (to avoid need of stack-evaluation) 2021-11-20 23:43:10 +01:00
Irmen de Jong 107935ed31 add some more const folding patterns 2021-11-20 22:47:49 +01:00
Irmen de Jong 31491c62c5 add some more const folding patterns 2021-11-20 22:40:12 +01:00
Irmen de Jong eacf8b896a fix augmentable check to align with what the asmgen understands 2021-11-20 22:06:51 +01:00
Irmen de Jong 7936fc5bd8 tiny optimization of negating a register 2021-11-20 21:42:55 +01:00
Irmen de Jong adfaddbcf4 give a nicer error when given a wrong compilation target. 2021-11-20 18:30:55 +01:00
Irmen de Jong 74db5c6be7 fix referencesIdentifier() and better removal of unnecessary assignments 2021-11-20 17:41:41 +01:00
Irmen de Jong f9399bcce7 r=(q+r)-c and r=q+(r-c) are now both also 'augmentable', and BinExprSplitter doesn't check for associativeOperator anymore 2021-11-20 02:03:32 +01:00
Irmen de Jong 87600b23db fix constvalue parent linkage for prefix and typecast 2021-11-20 00:20:35 +01:00
Irmen de Jong cedfb17b18 fix too aggressive removal of vars that weren't completely unused 2021-11-19 22:49:35 +01:00
Irmen de Jong fa4c83df6b added 3 tests for discovered problems 2021-11-18 23:55:20 +01:00
Irmen de Jong 42c8720e8b fix float rounding tests 2021-11-18 22:54:49 +01:00
Irmen de Jong b334d89715 refactor and fix the way memory addresses are checked to be in IO space or regular ram 2021-11-18 22:47:58 +01:00
Irmen de Jong 4f5d36a84d optimization added: bitwise operations with a negative constant number -> replace the number by its positive 2 complement 2021-11-18 02:51:42 +01:00
Irmen de Jong 8f379e2262 give an error when initializing an integer var with a float value instead of silently rounding 2021-11-18 01:56:11 +01:00
Irmen de Jong fa11a6e18b removed faulty and too aggressive assembly optimization of double-store 2021-11-18 01:43:22 +01:00
Irmen de Jong 52bedce8f4 added test for assignment.isAugmented 2021-11-18 01:05:16 +01:00
Irmen de Jong 4c82af36e6 fix improperly changed behavior about =0 initializer 2021-11-18 00:17:22 +01:00
Irmen de Jong dafa0d9138 fix compiler crash bug due to reused ast expression nodes. Now all (relevant) Nodes have a copy() function to make a clone. 2021-11-17 23:05:59 +01:00
Irmen de Jong 2e0450d7ed fix bug where variable=0 initializer was forgotten if vardecl is followed by an augmented assignment 2021-11-17 22:31:43 +01:00
Irmen de Jong 6af3209d4d add more const foldings 2021-11-17 00:57:00 +01:00
Irmen de Jong 5d362047e2 add some more comparison expression optimizations to compare against 0 if possible 2021-11-17 00:04:52 +01:00
Irmen de Jong f48d6ca9f8 simplified NumericLiteral to always just contain a Double instead of a Number for the value 2021-11-16 23:52:54 +01:00
Irmen de Jong 964e8e0a17 update to Kotlin 1.6.0 2021-11-16 22:36:23 +01:00
Irmen de Jong 1f60a2d8b9 comments 2021-11-15 01:30:12 +01:00
94 changed files with 3293 additions and 1771 deletions

No files matched your search

@@ -1,35 +1,41 @@
package prog8.compiler.target
import com.github.michaelbull.result.fold
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compilerinterface.ICompilationTarget
object C64Target: ICompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
else -> Int.MIN_VALUE
}
}
}
@@ -5,7 +5,12 @@ import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compiler.target.cx16.CX16MachineDefinition
import prog8.compilerinterface.ICompilationTarget
@@ -13,23 +18,28 @@ import prog8.compilerinterface.ICompilationTarget
object Cx16Target: ICompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
else -> Int.MIN_VALUE
}
}
}
@@ -16,12 +16,12 @@ object C64MachineDefinition: IMachineDefinition {
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
override val FLOAT_MEM_SIZE = 5
override val POINTER_MEM_SIZE = 2
override val BASIC_LOAD_ADDRESS = 0x0801
override val RAW_LOAD_ADDRESS = 0xc000
override val BASIC_LOAD_ADDRESS = 0x0801u
override val RAW_LOAD_ADDRESS = 0xc000u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
override val ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
override val ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
override lateinit var zeropage: Zeropage
@@ -56,7 +56,7 @@ object C64MachineDefinition: IMachineDefinition {
}
}
override fun isRegularRAMaddress(address: Int): Boolean = address<0xa000 || address in 0xc000..0xcfff
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = C64Zeropage(compilerOptions)
@@ -76,10 +76,10 @@ object C64MachineDefinition: IMachineDefinition {
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x02 // temp storage for a single byte
override val SCRATCH_REG = 0x03 // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0xfb // temp storage 1 for a word $fb+$fc
override val SCRATCH_W2 = 0xfd // temp storage 2 for a word $fb+$fc
override val SCRATCH_B1 = 0x02u // temp storage for a single byte
override val SCRATCH_REG = 0x03u // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fb+$fc
init {
@@ -87,9 +87,9 @@ object C64MachineDefinition: IMachineDefinition {
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
if (options.zeropage == ZeropageType.FULL) {
free.addAll(0x04..0xf9)
free.add(0xff)
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
free.addAll(0x04u..0xf9u)
free.add(0xffu)
free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
} else {
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
free.addAll(listOf(
@@ -106,7 +106,7 @@ object C64MachineDefinition: IMachineDefinition {
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
// 0x90-0xfa is 'kernal work storage area'
))
).map{it.toUInt()})
}
if (options.zeropage == ZeropageType.FLOATSAFE) {
@@ -118,7 +118,7 @@ object C64MachineDefinition: IMachineDefinition {
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
))
).map{it.toUInt()})
}
if(options.zeropage!= ZeropageType.DONTUSE) {
@@ -126,7 +126,7 @@ object C64MachineDefinition: IMachineDefinition {
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
0xb0, 0xb1, 0xbe, 0xbf, 0xf9))
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
} else {
// don't use the zeropage at all
free.clear()
@@ -137,11 +137,11 @@ object C64MachineDefinition: IMachineDefinition {
}
}
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short):
data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, val b4: UByte):
IMachineFloat {
companion object {
val zero = Mflpt5(0, 0, 0, 0, 0)
val zero = Mflpt5(0u, 0u, 0u, 0u, 0u)
fun fromNumber(num: Number): Mflpt5 {
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
@@ -175,11 +175,11 @@ object C64MachineDefinition: IMachineDefinition {
else -> {
val mantLong = mantissa.toLong()
Mflpt5(
exponent.toShort(),
(mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(),
(mantLong.and(0x00ff0000L) ushr 16).toShort(),
(mantLong.and(0x0000ff00L) ushr 8).toShort(),
(mantLong.and(0x000000ffL)).toShort())
exponent.toUByte(),
(mantLong.and(0x7f000000L) ushr 24).or(sign).toUByte(),
(mantLong.and(0x00ff0000L) ushr 16).toUByte(),
(mantLong.and(0x0000ff00L) ushr 8).toUByte(),
(mantLong.and(0x000000ffL)).toUByte())
}
}
}
@@ -187,7 +187,7 @@ object C64MachineDefinition: IMachineDefinition {
override fun toDouble(): Double {
if (this == zero) return 0.0
val exp = b0 - 128
val exp = b0.toInt() - 128
val sign = (b1.toInt() and 0x80) > 0
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
@@ -1065,15 +1065,15 @@ object Petscii {
else -> chr
}
fun encodePetscii(text: String, lowercase: Boolean = false): Result<List<Short>, CharConversionException> {
fun encodeChar(chr3: Char, lowercase: Boolean): Short {
fun encodePetscii(text: String, lowercase: Boolean = false): Result<List<UByte>, CharConversionException> {
fun encodeChar(chr3: Char, lowercase: Boolean): UByte {
val chr = replaceSpecial(chr3)
val screencode = if(lowercase) encodingPetsciiLowercase[chr] else encodingPetsciiUppercase[chr]
return screencode?.toShort() ?: when (chr) {
'\u0000' -> 0.toShort()
return screencode?.toUByte() ?: when (chr) {
'\u0000' -> 0u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toShort()
(chr.code - 0x8000).toUByte()
}
else -> {
val case = if (lowercase) "lower" else "upper"
@@ -1095,7 +1095,7 @@ object Petscii {
}
}
fun decodePetscii(petscii: Iterable<Short>, lowercase: Boolean = false): String {
fun decodePetscii(petscii: Iterable<UByte>, lowercase: Boolean = false): String {
return petscii.map {
val code = it.toInt()
if(code<0 || code>= decodingPetsciiLowercase.size)
@@ -1104,15 +1104,15 @@ object Petscii {
}.joinToString("")
}
fun encodeScreencode(text: String, lowercase: Boolean = false): Result<List<Short>, CharConversionException> {
fun encodeChar(chr3: Char, lowercase: Boolean): Short {
fun encodeScreencode(text: String, lowercase: Boolean = false): Result<List<UByte>, CharConversionException> {
fun encodeChar(chr3: Char, lowercase: Boolean): UByte {
val chr = replaceSpecial(chr3)
val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr]
return screencode?.toShort() ?: when (chr) {
'\u0000' -> 0.toShort()
return screencode?.toUByte() ?: when (chr) {
'\u0000' -> 0u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toShort()
(chr.code - 0x8000).toUByte()
}
else -> {
val case = if (lowercase) "lower" else "upper"
@@ -1134,7 +1134,7 @@ object Petscii {
}
}
fun decodeScreencode(screencode: Iterable<Short>, lowercase: Boolean = false): String {
fun decodeScreencode(screencode: Iterable<UByte>, lowercase: Boolean = false): String {
return screencode.map {
val code = it.toInt()
if(code<0 || code>= decodingScreencodeLowercase.size)
@@ -1143,38 +1143,37 @@ object Petscii {
}.joinToString("")
}
fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Result<Short, CharConversionException> {
val code = when {
petscii_code < 0 -> return Err(CharConversionException("petscii code out of range"))
petscii_code <= 0x1f -> petscii_code + 128
petscii_code <= 0x3f -> petscii_code.toInt()
petscii_code <= 0x5f -> petscii_code - 64
petscii_code <= 0x7f -> petscii_code - 32
petscii_code <= 0x9f -> petscii_code + 64
petscii_code <= 0xbf -> petscii_code - 64
petscii_code <= 0xfe -> petscii_code - 128
petscii_code == 255.toShort() -> 95
fun petscii2scr(petscii_code: UByte, inverseVideo: Boolean): Result<UByte, CharConversionException> {
val code: UInt = when {
petscii_code <= 0x1fu -> petscii_code + 128u
petscii_code <= 0x3fu -> petscii_code.toUInt()
petscii_code <= 0x5fu -> petscii_code - 64u
petscii_code <= 0x7fu -> petscii_code - 32u
petscii_code <= 0x9fu -> petscii_code + 64u
petscii_code <= 0xbfu -> petscii_code - 64u
petscii_code <= 0xfeu -> petscii_code - 128u
petscii_code == 255.toUByte() -> 95u
else -> return Err(CharConversionException("petscii code out of range"))
}
if(inverseVideo)
return Ok((code or 0x80).toShort())
return Ok(code.toShort())
if(inverseVideo) {
return Ok((code or 0x80u).toUByte())
}
return Ok(code.toUByte())
}
fun scr2petscii(screencode: Short): Result<Short, CharConversionException> {
val petscii = when {
screencode < 0 -> return Err(CharConversionException("screencode out of range"))
screencode <= 0x1f -> screencode + 64
screencode <= 0x3f -> screencode.toInt()
screencode <= 0x5d -> screencode +123
screencode == 0x5e.toShort() -> 255
screencode == 0x5f.toShort() -> 223
screencode <= 0x7f -> screencode + 64
screencode <= 0xbf -> screencode - 128
screencode <= 0xfe -> screencode - 64
screencode == 255.toShort() -> 191
fun scr2petscii(screencode: UByte): Result<UByte, CharConversionException> {
val petscii: UInt = when {
screencode <= 0x1fu -> screencode + 64u
screencode <= 0x3fu -> screencode.toUInt()
screencode <= 0x5du -> screencode +123u
screencode == 0x5e.toUByte() -> 255u
screencode == 0x5f.toUByte() -> 223u
screencode <= 0x7fu -> screencode + 64u
screencode <= 0xbfu -> screencode - 128u
screencode <= 0xfeu -> screencode - 64u
screencode == 255.toUByte() -> 191u
else -> return Err(CharConversionException("screencode out of range"))
}
return Ok(petscii.toShort())
return Ok(petscii.toUByte())
}
}
@@ -23,6 +23,7 @@ import java.time.LocalDate
import java.time.LocalDateTime
import java.util.*
import kotlin.io.path.Path
import kotlin.io.path.writeLines
import kotlin.math.absoluteValue
@@ -40,7 +41,7 @@ class AsmGen(private val program: Program,
private val assemblyLines = mutableListOf<String>()
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
private val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
private val allocatedZeropageVariables = mutableMapOf<String, Pair<UInt, DataType>>()
private val breakpointLabels = mutableListOf<String>()
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
@@ -49,7 +50,7 @@ class AsmGen(private val program: Program,
private val assignmentAsmGen = AssignmentAsmGen(program, this)
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
internal val loopEndLabels = ArrayDeque<String>()
internal val slabs = mutableMapOf<String, Int>()
internal val slabs = mutableMapOf<String, UInt>()
internal val removals = mutableListOf<Pair<Statement, IStatementContainer>>()
private val blockVariableInitializers = program.allBlocks.associateWith { it.statements.filterIsInstance<Assignment>() }
@@ -77,23 +78,15 @@ class AsmGen(private val program: Program,
slaballocations()
footer()
val outputFile = outputDir.resolve("${program.name}.asm").toFile()
outputFile.printWriter().use {
for (line in assemblyLines) { it.println(line) }
}
if(options.optimize) {
assemblyLines.clear()
assemblyLines.addAll(outputFile.readLines())
var optimizationsDone = 1
while (optimizationsDone > 0) {
optimizationsDone = optimizeAssembly(assemblyLines)
}
outputFile.printWriter().use {
for (line in assemblyLines) { it.println(line) }
optimizationsDone = optimizeAssembly(assemblyLines, options.compTarget.machine, program)
}
}
outputDir.resolve("${program.name}.asm").writeLines(assemblyLines)
return if(errors.noErrors())
AssemblyProgram(true, program.name, outputDir, compTarget.name)
else {
@@ -104,6 +97,11 @@ class AsmGen(private val program: Program,
internal fun isTargetCpu(cpu: CpuType) = compTarget.machine.cpu == cpu
internal fun haveFPWR() = compTarget is Cx16Target
internal fun asmsubArgsEvalOrder(sub: Subroutine) =
compTarget.asmsubArgsEvalOrder(sub)
internal fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
compTarget.asmsubArgsHaveRegisterClobberRisk(args, paramRegisters)
private fun header() {
val ourName = this.javaClass.name
val cpu = when(compTarget.machine.cpu) {
@@ -119,7 +117,7 @@ class AsmGen(private val program: Program,
out("\n.cpu '$cpu'\n.enc 'none'\n")
program.actualLoadAddress = program.definedLoadAddress
if (program.actualLoadAddress == 0) // fix load address
if (program.actualLoadAddress == 0u) // fix load address
program.actualLoadAddress = if (options.launcher == LauncherType.BASIC)
compTarget.machine.BASIC_LOAD_ADDRESS else compTarget.machine.RAW_LOAD_ADDRESS
@@ -134,7 +132,7 @@ class AsmGen(private val program: Program,
when {
options.launcher == LauncherType.BASIC -> {
if (program.actualLoadAddress != 0x0801)
if (program.actualLoadAddress != 0x0801u)
throw AssemblyError("BASIC output must have load address $0801")
out("; ---- basic program with sys call ----")
out("* = ${program.actualLoadAddress.toHex()}")
@@ -248,10 +246,9 @@ class AsmGen(private val program: Program,
internal fun out(str: String, splitlines: Boolean = true) {
val fragment = (if(" | " in str) str.replace("|", "\n") else str).trim('\n')
if (splitlines) {
for (line in fragment.split('\n')) {
val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line.trim()
for (line in fragment.splitToSequence('\n')) {
val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line
// trimmed = trimmed.replace(Regex("^\\+\\s+"), "+\t") // sanitize local label indentation
assemblyLines.add(trimmed)
}
@@ -265,7 +262,7 @@ class AsmGen(private val program: Program,
for(variable in variables) {
if(blockname=="prog8_lib" && variable.name.startsWith("P8ZP_SCRATCH_"))
continue // the "hooks" to the temp vars are not generated as new variables
val fullName = variable.makeScopedName(variable.name)
val fullName = variable.scopedName.joinToString(".")
val zpVar = allocatedZeropageVariables[fullName]
if(zpVar==null) {
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
@@ -297,7 +294,7 @@ class AsmGen(private val program: Program,
DataType.FLOAT -> out("$name\t.byte 0,0,0,0,0 ; float")
DataType.STR -> {
val str = decl.value as StringLiteralValue
outputStringvar(decl, compTarget.encodeString(str.value, str.altEncoding).plus(0))
outputStringvar(decl, compTarget.encodeString(str.value, str.altEncoding).plus(0.toUByte()))
}
DataType.ARRAY_UB -> {
val data = makeArrayFillDataUnsigned(decl)
@@ -393,7 +390,7 @@ class AsmGen(private val program: Program,
.filter {it.datatype == DataType.STR }
.map {
val str = it.value as StringLiteralValue
it to compTarget.encodeString(str.value, str.altEncoding).plus(0)
it to compTarget.encodeString(str.value, str.altEncoding).plus(0.toUByte())
}
for((decl, variables) in encodedstringVars) {
outputStringvar(decl, variables)
@@ -403,14 +400,15 @@ class AsmGen(private val program: Program,
val blockname = inBlock?.name
vars.filter{ it.datatype != DataType.STR }.sortedBy { it.datatype }.forEach {
if(it.makeScopedName(it.name) !in allocatedZeropageVariables) {
val scopedname = it.scopedName.joinToString(".")
if(scopedname !in allocatedZeropageVariables) {
if(blockname!="prog8_lib" || !it.name.startsWith("P8ZP_SCRATCH_")) // the "hooks" to the temp vars are not generated as new variables
vardecl2asm(it)
}
}
}
private fun outputStringvar(strdecl: VarDecl, bytes: List<Short>) {
private fun outputStringvar(strdecl: VarDecl, bytes: List<UByte>) {
val sv = strdecl.value as StringLiteralValue
val altEncoding = if(sv.altEncoding) "@" else ""
out("${strdecl.name}\t; ${strdecl.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "<NULL>")}\"")
@@ -521,16 +519,18 @@ class AsmGen(private val program: Program,
val targetScope = target.definingSubroutine
val identScope = identifier.definingSubroutine
return if (targetScope !== identScope) {
val scopedName = getScopedSymbolNameForTarget(identifier.nameInSource.last(), target)
val scopedName = (target as INamedStatement).scopedName
if (target is Label) {
// make labels locally scoped in the asm. Is slightly problematic, see github issue #62
val last = scopedName.removeLast()
scopedName.add("_$last")
// make labels locally scoped in the asm. Is slightly problematic, see GitHub issue #62
val newName = scopedName.dropLast(1) + ("_${scopedName.last()}")
fixNameSymbols(newName.joinToString("."))
}
else {
fixNameSymbols(scopedName.joinToString("."))
}
fixNameSymbols(scopedName.joinToString("."))
} else {
if (target is Label) {
// make labels locally scoped in the asm. Is slightly problematic, see github issue #62
// make labels locally scoped in the asm. Is slightly problematic, see GitHub issue #62
val scopedName = identifier.nameInSource.toMutableList()
val last = scopedName.removeLast()
scopedName.add("_$last")
@@ -545,18 +545,6 @@ class AsmGen(private val program: Program,
fun asmVariableName(identifier: IdentifierReference) =
fixNameSymbols(identifier.nameInSource.joinToString("."))
private fun getScopedSymbolNameForTarget(actualName: String, target: Statement): MutableList<String> {
val scopedName = mutableListOf(actualName)
var node: Node = target
while (node !is Block) {
node = node.parent
if(node is INameScope) {
scopedName.add(0, node.name)
}
}
return scopedName
}
internal fun asmSymbolName(regs: RegisterOrPair): String =
if (regs in Cx16VirtualRegisters)
"cx16." + regs.toString().lowercase()
@@ -580,7 +568,7 @@ class AsmGen(private val program: Program,
}
is VarDecl -> {
val sourceName = asmVariableName(pointervar)
val scopedName = target.makeScopedName(target.name)
val scopedName = target.scopedName.joinToString(".")
if (isTargetCpu(CpuType.CPU65c02)) {
return if (isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
@@ -720,7 +708,6 @@ class AsmGen(private val program: Program,
internal fun translate(stmt: Statement) {
outputSourceLine(stmt)
when(stmt) {
is ParameterVarDecl -> { /* subroutine parameter vardecls don't get any special treatment here */ }
is VarDecl -> translate(stmt)
is NopStatement -> {}
is Directive -> translate(stmt)
@@ -858,15 +845,21 @@ class AsmGen(private val program: Program,
internal fun translateBuiltinFunctionCallExpression(functionCall: FunctionCall, signature: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature, resultToStack, resultRegister)
internal fun translateFunctionCall(functionCall: FunctionCall) =
functioncallAsmGen.translateFunctionCall(functionCall)
internal fun translateFunctionCall(functionCall: FunctionCall, isExpression: Boolean) =
functioncallAsmGen.translateFunctionCall(functionCall, isExpression)
internal fun saveXbeforeCall(functionCall: IFunctionCall) =
functioncallAsmGen.saveXbeforeCall(functionCall)
internal fun saveXbeforeCall(gosub: GoSub) =
functioncallAsmGen.saveXbeforeCall(gosub)
internal fun restoreXafterCall(functionCall: IFunctionCall) =
functioncallAsmGen.restoreXafterCall(functionCall)
internal fun restoreXafterCall(gosub: GoSub) =
functioncallAsmGen.restoreXafterCall(gosub)
internal fun translateNormalAssignment(assign: AsmAssignment) =
assignmentAsmGen.translateNormalAssignment(assign)
@@ -889,7 +882,7 @@ class AsmGen(private val program: Program,
RegisterOrPair.XY,
in Cx16VirtualRegisters -> assignmentAsmGen.assignRegisterpairWord(target, reg)
RegisterOrPair.FAC1 -> assignmentAsmGen.assignFAC1float(target)
RegisterOrPair.FAC2 -> TODO("no support yet to assign FAC2 directly to something")
RegisterOrPair.FAC2 -> assignmentAsmGen.assignFAC2float(target)
else -> throw AssemblyError("invalid register")
}
}
@@ -1283,7 +1276,7 @@ $repeatLabel lda $counterVar
}
private fun translate(stmt: Label) {
// underscore prefix to make sure it's a local label. Is slightly problematic, see github issue #62
// underscore prefix to make sure it's a local label. Is slightly problematic, see GitHub issue #62
out("_${stmt.name}")
}
@@ -1298,6 +1291,8 @@ $repeatLabel lda $counterVar
val jump = stmt.truepart.statements.first() as? Jump
if(jump!=null) {
if(jump.isGosub)
throw FatalAstException("didn't expect GoSub here")
// branch with only a jump (goto)
val instruction = branchInstruction(stmt.condition, false)
out(" $instruction ${getJumpTarget(jump)}")
@@ -1387,7 +1382,21 @@ $label nop""")
}
}
private fun translate(jump: Jump) = jmp(getJumpTarget(jump))
private fun translate(jump: Jump) {
if(jump.isGosub) {
val tgt = jump.identifier!!.targetSubroutine(program)
if(tgt!=null && tgt.isAsmSubroutine) {
// no need to rescue X , this has been taken care of already
out(" jsr ${getJumpTarget(jump)}")
} else {
saveXbeforeCall(jump as GoSub)
out(" jsr ${getJumpTarget(jump)}")
restoreXafterCall(jump as GoSub)
}
}
else
jmp(getJumpTarget(jump))
}
private fun getJumpTarget(jump: Jump): String {
val ident = jump.identifier
@@ -1401,7 +1410,7 @@ $label nop""")
}
}
internal fun translate(ret: Return, withRts: Boolean=true) {
private fun translate(ret: Return, withRts: Boolean=true) {
ret.value?.let { returnvalue ->
val sub = ret.definingSubroutine!!
val returnType = sub.returntypes.single()
@@ -1478,7 +1487,7 @@ $label nop""")
internal fun isZpVar(variable: IdentifierReference): Boolean {
val vardecl = variable.targetVarDecl(program)!!
return vardecl.makeScopedName(vardecl.name) in allocatedZeropageVariables
return vardecl.scopedName.joinToString(".") in allocatedZeropageVariables
}
internal fun jmp(asmLabel: String) {
@@ -1611,7 +1620,7 @@ $label nop""")
}
}
if (rightConstVal!=null && rightConstVal.number.toDouble() == 0.0)
if (rightConstVal!=null && rightConstVal.number == 0.0)
jumpIfZeroOrNot(left, operator, jumpIfFalseLabel)
else
jumpIfComparison(left, operator, right, jumpIfFalseLabel, leftConstVal, rightConstVal)
@@ -1791,7 +1800,7 @@ $label nop""")
}
else if(left is IdentifierReference && rightConstVal!=null) {
val leftName = asmVariableName(left)
val rightName = getFloatAsmConst(rightConstVal.number.toDouble())
val rightName = getFloatAsmConst(rightConstVal.number)
out("""
lda #<$rightName
ldy #>$rightName
@@ -1836,7 +1845,7 @@ $label nop""")
}
else if(left is IdentifierReference && rightConstVal!=null) {
val leftName = asmVariableName(left)
val rightName = getFloatAsmConst(rightConstVal.number.toDouble())
val rightName = getFloatAsmConst(rightConstVal.number)
out("""
lda #<$rightName
ldy #>$rightName
@@ -1878,7 +1887,7 @@ $label nop""")
}
else if(left is IdentifierReference && rightConstVal!=null) {
val leftName = asmVariableName(left)
val rightName = getFloatAsmConst(rightConstVal.number.toDouble())
val rightName = getFloatAsmConst(rightConstVal.number)
out("""
lda #<$leftName
ldy #>$leftName
@@ -1923,7 +1932,7 @@ $label nop""")
}
else if(left is IdentifierReference && rightConstVal!=null) {
val leftName = asmVariableName(left)
val rightName = getFloatAsmConst(rightConstVal.number.toDouble())
val rightName = getFloatAsmConst(rightConstVal.number)
out("""
lda #<$leftName
ldy #>$leftName
@@ -2821,7 +2830,7 @@ $label nop""")
} else {
if (left is IdentifierReference) {
val name = asmVariableName(left)
when(rightConstVal.number.toDouble())
when(rightConstVal.number)
{
0.0 -> {
out("""
@@ -2860,7 +2869,7 @@ $label nop""")
}
else if(left is IdentifierReference && rightConstVal!=null) {
val leftName = asmVariableName(left)
val rightName = getFloatAsmConst(rightConstVal.number.toDouble())
val rightName = getFloatAsmConst(rightConstVal.number)
out("""
lda #<$leftName
ldy #>$leftName
@@ -2905,7 +2914,7 @@ $label nop""")
} else {
if (left is IdentifierReference) {
val name = asmVariableName(left)
when(rightConstVal.number.toDouble())
when(rightConstVal.number)
{
0.0 -> {
out("""
@@ -2945,7 +2954,7 @@ $label nop""")
}
else if(left is IdentifierReference && rightConstVal!=null) {
val leftName = asmVariableName(left)
val rightName = getFloatAsmConst(rightConstVal.number.toDouble())
val rightName = getFloatAsmConst(rightConstVal.number)
out("""
lda #<$leftName
ldy #>$leftName
@@ -1,10 +1,16 @@
package prog8.compiler.target.cpu6502.codegen
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
import prog8.ast.Program
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.VarDecl
import prog8.compilerinterface.IMachineDefinition
fun optimizeAssembly(lines: MutableList<String>): Int {
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, program: Program): Int {
var numberOfOptimizations = 0
@@ -31,7 +37,7 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
numberOfOptimizations++
}
mods = optimizeStoreLoadSame(linesByFour)
mods = optimizeStoreLoadSame(linesByFour, machine, program)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
@@ -46,7 +52,7 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
}
var linesByFourteen = getLinesBy(lines, 14)
mods = optimizeSameAssignments(linesByFourteen)
mods = optimizeSameAssignments(linesByFourteen, machine, program)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFourteen = getLinesBy(lines, 14)
@@ -111,11 +117,11 @@ private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<S
return mods
}
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
// Optimize sequential assignments of the same value to various targets (bytes, words, floats)
// the float one is the one that requires 2*7=14 lines of code to check...
// @todo a better place to do this is in the Compiler instead and transform the Ast, or the AsmGen, and never even create the inefficient asm in the first place...
// The better place to do this is in the Compiler instead and never create these types of assembly, but hey
val mods = mutableListOf<Modification>()
for (lines in linesByFourteen) {
@@ -135,9 +141,13 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val thirdvalue = fifth.substring(4)
val fourthvalue = sixth.substring(4)
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
// lda/ldy sta/sty twice the isSameAs word --> remove second lda/ldy pair (fifth and sixth lines)
mods.add(Modification(lines[4].index, true, null))
mods.add(Modification(lines[5].index, true, null))
// lda/ldy sta/sty twice the same word --> remove second lda/ldy pair (fifth and sixth lines)
val address1 = getAddressArg(first, program)
val address2 = getAddressArg(second, program)
if(address1==null || address2==null || (!machine.isIOAddress(address1) && !machine.isIOAddress(address2))) {
mods.add(Modification(lines[4].index, true, null))
mods.add(Modification(lines[5].index, true, null))
}
}
}
@@ -145,8 +155,10 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val firstvalue = first.substring(4)
val secondvalue = third.substring(4)
if(firstvalue==secondvalue) {
// lda value / sta ? / lda isSameAs-value / sta ? -> remove second lda (third line)
mods.add(Modification(lines[2].index, true, null))
// lda value / sta ? / lda same-value / sta ? -> remove second lda (third line)
val address = getAddressArg(first, program)
if(address==null || !machine.isIOAddress(address))
mods.add(Modification(lines[2].index, true, null))
}
}
@@ -227,10 +239,13 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val thirdvalue = third.substring(4)
val fourthvalue = fourth.substring(4)
if(firstvalue==thirdvalue && secondvalue == fourthvalue) {
overlappingMods = true
mods.add(Modification(lines[2].index, true, null))
if(!fifth.startsWith('b'))
mods.add(Modification(lines[3].index, true, null))
val address = getAddressArg(first, program)
if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true
mods.add(Modification(lines[2].index, true, null))
if (!fifth.startsWith('b'))
mods.add(Modification(lines[3].index, true, null))
}
}
}
}
@@ -247,29 +262,15 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val firstvalue = first.substring(4)
val thirdvalue = third.substring(4)
if(firstvalue==thirdvalue) {
overlappingMods = true
mods.add(Modification(lines[2].index, true, null))
}
}
}
/*
sta A1
<other st/ld instruction not involving A1>
sta A1 ; can be removed
*/
if(!overlappingMods && first.startsWith("st") && third.startsWith("st")) {
val reg1 = first[2]
val reg3 = third[2]
if(reg1==reg3 && (second.startsWith("ld") || second.startsWith("st"))) {
val firstvalue = first.substring(4)
val secondvalue = second.substring(4)
val thirdvalue = third.substring(4)
if(firstvalue==thirdvalue && firstvalue!=secondvalue) {
overlappingMods = true
mods.add(Modification(lines[2].index, true, null))
val address = getAddressArg(first, program)
if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true
mods.add(Modification(lines[2].index, true, null))
}
}
}
}
/*
sta A1
ldy A1 ; make tay
@@ -281,10 +282,31 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val secondvalue = second.substring(4)
val thirdvalue = third.substring(4)
if(firstvalue==secondvalue && firstvalue==thirdvalue) {
overlappingMods = true
val reg2 = second[2]
mods.add(Modification(lines[1].index, false, " ta$reg2"))
mods.add(Modification(lines[2].index, true, null))
val address = getAddressArg(first, program)
if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true
val reg2 = second[2]
mods.add(Modification(lines[1].index, false, " ta$reg2"))
mods.add(Modification(lines[2].index, true, null))
}
}
}
/*
sta A
sta A
*/
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")) {
if(first[2]==second[2]) {
val firstvalue = first.substring(4)
val secondvalue = second.substring(4)
if(firstvalue==secondvalue) {
val address = getAddressArg(first, program)
if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true
mods.add(Modification(lines[1].index, true, null))
}
}
}
}
}
@@ -292,9 +314,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
return mods
}
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
// TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM) but how does this code know?
val mods = mutableListOf<Modification>()
for (lines in linesByFour) {
val first = lines[1].value.trimStart()
@@ -321,7 +342,8 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
}
else {
// no branch instruction follows, we can remove the load instruction
true
val address = getAddressArg(lines[2].value, program)
address==null || !machine.isIOAddress(address)
}
if(attemptRemove) {
@@ -331,10 +353,63 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
mods.add(Modification(lines[2].index, true, null))
}
}
else if(first=="pha" && second=="pla" ||
first=="phx" && second=="plx" ||
first=="phy" && second=="ply" ||
first=="php" && second=="plp") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
} else if(first=="pha" && second=="plx") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " tax"))
} else if(first=="pha" && second=="ply") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " tay"))
} else if(first=="phx" && second=="pla") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " txa"))
} else if(first=="phx" && second=="ply") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " txy"))
} else if(first=="phy" && second=="pla") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " tya"))
} else if(first=="phy" && second=="plx") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, false, " tyx"))
}
}
return mods
}
private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_\.$]*)""")
private fun getAddressArg(line: String, program: Program): UInt? {
val loadArg = line.trimStart().substring(3).trim()
return when {
loadArg.startsWith('$') -> loadArg.substring(1).toUIntOrNull(16)
loadArg.startsWith('%') -> loadArg.substring(1).toUIntOrNull(2)
loadArg.startsWith('#') -> null
loadArg.startsWith('(') -> null
loadArg[0].isLetter() -> {
val identMatch = identifierRegex.find(loadArg)
if(identMatch!=null) {
val identifier = identMatch.value
val decl = program.toplevelModule.lookup(identifier.split(".")) as? VarDecl
if(decl!=null) {
when(decl.type){
VarDeclType.VAR -> null
VarDeclType.CONST,
VarDeclType.MEMORY -> (decl.value as NumericLiteralValue).number.toUInt()
}
}
else null
} else null
}
else -> loadArg.substring(1).toUIntOrNull()
}
}
private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sometimes, iny+dey / inx+dex / dey+iny / dex+inx sequences are generated, these can be eliminated.
val mods = mutableListOf<Modification>()
@@ -343,8 +418,12 @@ private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<
val second = lines[1].value
if ((" iny" in first || "\tiny" in first) && (" dey" in second || "\tdey" in second)
|| (" inx" in first || "\tinx" in first) && (" dex" in second || "\tdex" in second)
|| (" ina" in first || "\tina" in first) && (" dea" in second || "\tdea" in second)
|| (" inc a" in first || "\tinc a" in first) && (" dec a" in second || "\tdec a" in second)
|| (" dey" in first || "\tdey" in first) && (" iny" in second || "\tiny" in second)
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)) {
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)
|| (" dea" in first || "\tdea" in first) && (" ina" in second || "\tina" in second)
|| (" dec a" in first || "\tdec a" in first) && (" inc a" in second || "\tinc a" in second)) {
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, true, null))
}
@@ -0,0 +1,59 @@
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.base.Cx16VirtualRegisters
import prog8.ast.base.RegisterOrPair
import prog8.ast.expressions.*
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
internal fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> {
val order = mutableListOf<Int>()
// order is:
// 1) cx16 virtual word registers,
// 2) paired CPU registers,
// 3) single CPU registers (X last), except A,
// 4) CPU Carry status flag
// 5) the A register itself last (so everything before it can use the accumulator without having to save its value)
val args = sub.parameters.zip(sub.asmParameterRegisters).withIndex()
val (cx16regs, args2) = args.partition { it.value.second.registerOrPair in Cx16VirtualRegisters }
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
val (pairedRegs , args3) = args2.partition { it.value.second.registerOrPair in pairedRegisters }
val (regsWithoutA, args4) = args3.partition { it.value.second.registerOrPair != RegisterOrPair.A }
val (regA, rest) = args4.partition { it.value.second.registerOrPair != null }
cx16regs.forEach { order += it.index }
pairedRegs.forEach { order += it.index }
regsWithoutA.forEach {
if(it.value.second.registerOrPair != RegisterOrPair.X)
order += it.index
}
regsWithoutA.firstOrNull { it.value.second.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
rest.forEach { order += it.index }
regA.forEach { order += it.index }
require(order.size==sub.parameters.size)
return order
}
internal fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>,
paramRegisters: List<RegisterOrStatusflag>): Boolean {
fun isClobberRisk(expr: Expression): Boolean {
when (expr) {
is ArrayIndexedExpression -> {
return paramRegisters.any {
it.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
}
}
is FunctionCall -> {
if (expr.target.nameInSource == listOf("lsb") || expr.target.nameInSource == listOf("msb"))
return isClobberRisk(expr.args[0])
if (expr.target.nameInSource == listOf("mkword"))
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
return !expr.isSimple
}
else -> return !expr.isSimple
}
}
return args.size>1 && args.any { isClobberRisk(it) }
}
@@ -67,6 +67,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
"pokew" -> funcPokeW(fcall)
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
"push", "pushw" -> funcPush(fcall, func)
"pop", "popw" -> funcPop(fcall, func)
"rsave" -> funcRsave()
"rsavex" -> funcRsaveX()
"rrestore" -> funcRrestore()
"rrestorex" -> funcRrestoreX()
"cmp" -> funcCmp(fcall)
"callfar" -> funcCallFar(fcall)
"callrom" -> funcCallRom(fcall)
@@ -74,6 +80,168 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
private fun funcRsave() {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out("""
php
pha
phy
phx""")
else
// see http://6502.org/tutorials/register_preservation.html
asmgen.out("""
php
sta P8ZP_SCRATCH_REG
pha
txa
pha
tya
pha
lda P8ZP_SCRATCH_REG""")
}
private fun funcRsaveX() {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" phx")
else
asmgen.out(" txa | pha")
}
private fun funcRrestore() {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out("""
plx
ply
pla
plp""")
else
asmgen.out("""
pla
tay
pla
tax
pla
plp""")
}
private fun funcRrestoreX() {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" plx")
else
asmgen.out(" sta P8ZP_SCRATCH_B1 | pla | tax | lda P8ZP_SCRATCH_B1")
}
private fun funcPop(fcall: IFunctionCall, func: FSignature) {
// note: because A is pushed first so popped last, saving A is often not required here.
require(fcall.args[0] is IdentifierReference) {
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${(fcall as Node).position}"
}
val target = (fcall.args[0] as IdentifierReference).targetVarDecl(program)!!
val parameter = target.subroutineParameter
if(parameter!=null) {
val sub = parameter.definingSubroutine!!
require(sub.isAsmSubroutine) {
"push/pop arg passing only supported on asmsubs ${(fcall as Node).position}"
}
val shouldKeepA = sub.asmParameterRegisters.any { it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY }
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
if(reg.statusflag!=null) {
if(shouldKeepA)
asmgen.out(" sta P8ZP_SCRATCH_REG")
asmgen.out("""
clc
pla
beq +
sec
+""")
if(shouldKeepA)
asmgen.out(" lda P8ZP_SCRATCH_REG")
}
else {
if (func.name == "pop") {
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
when (reg.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" pla")
RegisterOrPair.X -> asmgen.out(" plx")
RegisterOrPair.Y -> asmgen.out(" ply")
in Cx16VirtualRegisters -> asmgen.out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
} else {
when (reg.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" pla")
RegisterOrPair.X -> {
if(shouldKeepA)
asmgen.out(" sta P8ZP_SCRATCH_REG | pla | tax | lda P8ZP_SCRATCH_REG")
else
asmgen.out(" pla | tax")
}
RegisterOrPair.Y -> {
if(shouldKeepA)
asmgen.out(" sta P8ZP_SCRATCH_REG | pla | tay | lda P8ZP_SCRATCH_REG")
else
asmgen.out(" pla | tay")
}
in Cx16VirtualRegisters -> asmgen.out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
}
} else {
// word pop
if (asmgen.isTargetCpu(CpuType.CPU65c02))
when (reg.registerOrPair) {
RegisterOrPair.AX -> asmgen.out(" plx | pla")
RegisterOrPair.AY -> asmgen.out(" ply | pla")
RegisterOrPair.XY -> asmgen.out(" ply | plx")
in Cx16VirtualRegisters -> {
val regname = reg.registerOrPair!!.name.lowercase()
asmgen.out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
}
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
else {
when (reg.registerOrPair) {
RegisterOrPair.AX -> asmgen.out(" pla | tax | pla")
RegisterOrPair.AY -> asmgen.out(" pla | tay | pla")
RegisterOrPair.XY -> asmgen.out(" pla | tay | pla | tax")
in Cx16VirtualRegisters -> {
val regname = reg.registerOrPair!!.name.lowercase()
asmgen.out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
}
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
}
}
}
} else {
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, target.datatype, (fcall as Node).definingSubroutine, variableAsmName = asmgen.asmVariableName(target.name))
if (func.name == "pop") {
asmgen.out(" pla")
asmgen.assignRegister(RegisterOrPair.A, tgt)
} else {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" ply | pla")
else
asmgen.out(" pla | tay | pla")
asmgen.assignRegister(RegisterOrPair.AY, tgt)
}
}
}
private fun funcPush(fcall: IFunctionCall, func: FSignature) {
val signed = fcall.args[0].inferType(program).oneOf(DataType.BYTE, DataType.WORD)
if(func.name=="push") {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed)
asmgen.out(" pha")
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" pha | phy")
else
asmgen.out(" pha | tya | pha")
}
}
private fun funcCallFar(fcall: IFunctionCall) {
if(asmgen.options.compTarget !is Cx16Target)
throw AssemblyError("callfar only works on cx16 target at this time")
@@ -89,7 +257,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
throw AssemblyError("callfar done on bank 0 which is reserved for the kernal")
val argAddrArg = fcall.args[2]
if(argAddrArg.constValue(program)?.number == 0) {
if(argAddrArg.constValue(program)?.number == 0.0) {
asmgen.out("""
jsr cx16.jsrfar
.word ${address.toHex()}
@@ -134,7 +302,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
throw AssemblyError("callrom bank must be <32")
val argAddrArg = fcall.args[2]
if(argAddrArg.constValue(program)?.number == 0) {
if(argAddrArg.constValue(program)?.number == 0.0) {
asmgen.out("""
lda $01
pha
@@ -250,7 +418,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
throw AssemblyError("should not discard result of memory allocation at $fcall")
val nameRef = fcall.args[0] as IdentifierReference
val name = (nameRef.targetVarDecl(program)!!.value as StringLiteralValue).value
val size = (fcall.args[1] as NumericLiteralValue).number.toInt()
val size = (fcall.args[1] as NumericLiteralValue).number.toUInt()
val existingSize = asmgen.slabs[name]
if(existingSize!=null && existingSize!=size)
@@ -1231,6 +1399,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
}
else -> throw AssemblyError("wrong pokew arg type")
}
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", DataType.UWORD, null)
@@ -53,7 +53,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
} else {
sub as Subroutine
asmgen.saveXbeforeCall(call)
asmgen.translateFunctionCall(call)
asmgen.translateFunctionCall(call, true)
if(sub.regXasResult()) {
// store the return value in X somewhere that we can acces again below
asmgen.out(" stx P8ZP_SCRATCH_REG")
@@ -64,7 +64,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
for ((_, reg) in returns) {
// result value is in cpu or status registers, put it on the stack instead (as we're evaluating an expression tree)
if (reg.registerOrPair != null) {
when (reg.registerOrPair) {
when (reg.registerOrPair!!) {
RegisterOrPair.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
RegisterOrPair.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
@@ -103,7 +103,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
Statusflag.Pc -> {
asmgen.out("""
lda #0
adc #0
rol a
sta P8ESTACK_LO,x
dex""")
}
@@ -220,7 +220,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
dex
""")
DataType.FLOAT -> {
val floatConst = asmgen.getFloatAsmConst(expr.number.toDouble())
val floatConst = asmgen.getFloatAsmConst(expr.number)
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
}
else -> throw AssemblyError("weird type")
@@ -43,7 +43,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
val stepsize=range.step.constValue(program)!!.number.toInt()
if(stepsize < -1) {
val limit = range.to.constValue(program)?.number?.toDouble()
val limit = range.to.constValue(program)?.number
if(limit==0.0)
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
}
@@ -5,10 +5,7 @@ import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.InlineAssembly
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.ast.statements.*
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
@@ -21,7 +18,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
saveXbeforeCall(stmt)
translateFunctionCall(stmt)
translateFunctionCall(stmt, false)
restoreXafterCall(stmt)
// just ignore any result values from the function call.
}
@@ -37,6 +34,17 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
}
internal fun saveXbeforeCall(gosub: GoSub) {
val sub = gosub.identifier?.targetSubroutine(program)
if(sub?.shouldSaveX()==true) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
if(regSaveOnStack)
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
else
asmgen.saveRegisterLocal(CpuRegister.X, gosub.definingSubroutine!!)
}
}
internal fun restoreXafterCall(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(sub.shouldSaveX()) {
@@ -48,90 +56,76 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
}
internal fun translateFunctionCall(stmt: IFunctionCall) {
internal fun restoreXafterCall(gosub: GoSub) {
val sub = gosub.identifier?.targetSubroutine(program)
if(sub?.shouldSaveX()==true) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
if(regSaveOnStack)
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
else
asmgen.restoreRegisterLocal(CpuRegister.X)
}
}
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) {
// Output only the code to set up the parameters and perform the actual call
// NOTE: does NOT output the code to deal with the result values!
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
val subName = asmgen.asmSymbolName(stmt.target)
if(stmt.args.isNotEmpty()) {
val sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}")
val subAsmName = asmgen.asmSymbolName(call.target)
if(sub.asmParameterRegisters.isEmpty()) {
// via variables
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaVariable(sub, arg.first, arg.second)
}
if(!isExpression && !sub.isAsmSubroutine)
throw AssemblyError("functioncall statements to non-asmsub should have been replaced by GoSub $call")
if(sub.isAsmSubroutine) {
argumentsViaRegisters(sub, call)
if (sub.inline && asmgen.options.optimize) {
// inline the subroutine.
// we do this by copying the subroutine's statements at the call site.
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
// (this condition has been enforced by an ast check earlier)
asmgen.out(" \t; inlined routine follows: ${sub.name}")
val assembly = sub.statements.single() as InlineAssembly
asmgen.translate(assembly)
asmgen.out(" \t; inlined routine end: ${sub.name}")
} else {
require(sub.isAsmSubroutine)
if(sub.parameters.size==1) {
// just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
} else {
fun isNoClobberRisk(expr: Expression): Boolean {
if(expr is AddressOf ||
expr is NumericLiteralValue ||
expr is StringLiteralValue ||
expr is ArrayLiteralValue ||
expr is IdentifierReference)
return true
if(expr is FunctionCall) {
if(expr.target.nameInSource==listOf("lsb") || expr.target.nameInSource==listOf("msb"))
return isNoClobberRisk(expr.args[0])
if(expr.target.nameInSource==listOf("mkword"))
return isNoClobberRisk(expr.args[0]) && isNoClobberRisk(expr.args[1])
}
return false
}
when {
stmt.args.all {isNoClobberRisk(it)} -> {
// There's no risk of clobbering for these simple argument types. Optimize the register loading directly from these values.
// register assignment order: 1) cx16 virtual word registers, 2) actual CPU registers, 3) CPU Carry status flag.
val argsInfo = sub.parameters.withIndex().zip(stmt.args).zip(sub.asmParameterRegisters)
val (cx16virtualRegs, args2) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
val (cpuRegs, statusRegs) = args2.partition { it.second.registerOrPair!=null }
for(arg in cx16virtualRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in cpuRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in statusRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
}
else -> {
// Risk of clobbering due to complex expression args. Evaluate first, then assign registers.
registerArgsViaStackEvaluation(stmt, sub)
}
}
}
asmgen.out(" jsr $subAsmName")
}
}
if(!sub.inline || !asmgen.options.optimize) {
asmgen.out(" jsr $subName")
} else {
// inline the subroutine.
// we do this by copying the subroutine's statements at the call site.
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
// (this condition has been enforced by an ast check earlier)
// note: for now, this is only reliably supported for asmsubs.
if(!sub.isAsmSubroutine)
else {
if(sub.inline)
throw AssemblyError("can only reliably inline asmsub routines at this time")
asmgen.out(" \t; inlined routine follows: ${sub.name}")
val assembly = sub.statements.single() as InlineAssembly
asmgen.translate(assembly)
asmgen.out(" \t; inlined routine end: ${sub.name}")
argumentsViaVariables(sub, call)
asmgen.out(" jsr $subAsmName")
}
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
}
private fun argumentsViaVariables(sub: Subroutine, call: IFunctionCall) {
for(arg in sub.parameters.withIndex().zip(call.args))
argumentViaVariable(sub, arg.first, arg.second)
}
private fun argumentsViaRegisters(sub: Subroutine, call: IFunctionCall) {
if(sub.parameters.size==1) {
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), call.args[0])
} else {
if(asmgen.asmsubArgsHaveRegisterClobberRisk(call.args, sub.asmParameterRegisters)) {
registerArgsViaStackEvaluation(call, sub)
} else {
asmgen.asmsubArgsEvalOrder(sub).forEach {
val param = sub.parameters[it]
val arg = call.args[it]
argumentViaRegister(sub, IndexedValue(it, param), arg)
}
}
}
}
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
// this is called when one or more of the arguments are 'complex' and
// cannot be assigned to a register easily or risk clobbering other registers.
@@ -220,11 +214,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
if(argForCarry!=null) {
val plusIdxStr = if(argForCarry.index==0) "" else "+${argForCarry.index}"
asmgen.out("""
clc
lda P8ESTACK_LO$plusIdxStr,x
beq +
sec
bcs ++
+ clc
+ php""") // push the status flags
}
@@ -267,7 +260,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
val varName = asmgen.asmVariableName(sub.scopedName + parameter.value.name)
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
}
@@ -303,11 +296,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val sourceName = asmgen.asmVariableName(value)
asmgen.out("""
pha
clc
lda $sourceName
beq +
sec
bcs ++
+ clc
+ pla
""")
}
@@ -335,8 +327,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val target: AsmAssignTarget =
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
else
AsmAssignTarget.fromRegisters(register, false, sub, program, asmgen)
else {
val signed = parameter.value.type == DataType.BYTE || parameter.value.type == DataType.WORD
AsmAssignTarget.fromRegisters(register, signed, sub, program, asmgen)
}
val src = if(valueDt in PassByReferenceDatatypes) {
if(value is IdentifierReference) {
val addr = AddressOf(value, Position.DUMMY)
@@ -39,8 +39,8 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
val origAstTarget: AssignTarget? = null
)
{
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toUInt() ?: 0u}
val constArrayIndexValue by lazy { array?.indexer?.constIndex()?.toUInt() }
val asmVarname: String by lazy {
if (array == null)
variableAsmName!!
@@ -56,16 +56,31 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
}
companion object {
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
val idt = inferType(program)
if(!idt.isKnown)
throw AssemblyError("unknown dt")
val dt = idt.getOr(DataType.UNDEFINED)
when {
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
else -> throw AssemblyError("weird target")
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget {
with(assign.target) {
val idt = inferType(program)
if(!idt.isKnown)
throw AssemblyError("unknown dt")
val dt = idt.getOr(DataType.UNDEFINED)
when {
identifier != null -> {
val parameter = identifier!!.targetVarDecl(program)?.subroutineParameter
if (parameter!=null) {
val sub = parameter.definingSubroutine!!
if (sub.isAsmSubroutine) {
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
if(reg.statusflag!=null)
throw AssemblyError("can't assign value to processor statusflag directly")
else
return AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, dt, assign.definingSubroutine, register=reg.registerOrPair, origAstTarget = this)
}
}
return AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
}
arrayindexed != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
memoryAddress != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
else -> throw AssemblyError("weird target")
}
}
}
@@ -111,8 +126,8 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val expression: Expression? = null
)
{
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toUInt() ?: 0u}
val constArrayIndexValue by lazy { array?.indexer?.constIndex()?.toUInt() }
val asmVarname: String
get() = if(array==null)
@@ -133,6 +148,9 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
is IdentifierReference -> {
val parameter = value.targetVarDecl(program)?.subroutineParameter
if(parameter!=null && parameter.definingSubroutine!!.isAsmSubroutine)
throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}")
val dt = value.inferType(program).getOr(DataType.UNDEFINED)
val varName=asmgen.asmVariableName(value)
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
@@ -29,6 +29,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
translateNormalAssignment(assign)
}
internal fun virtualRegsToVariables(origtarget: AsmAssignTarget): AsmAssignTarget {
return if(origtarget.kind==TargetStorageKind.REGISTER && origtarget.register in Cx16VirtualRegisters) {
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, origtarget.datatype, origtarget.scope,
variableAsmName = "cx16.${origtarget.register!!.name.lowercase()}", origAstTarget = origtarget.origAstTarget)
} else origtarget
}
fun translateNormalAssignment(assign: AsmAssignment) {
if(assign.isAugmentable) {
augmentableAsmGen.translate(assign)
@@ -40,9 +47,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
// simple case: assign a constant number
val num = assign.source.number!!.number
when (assign.target.datatype) {
DataType.UBYTE, DataType.BYTE -> assignConstantByte(assign.target, num.toShort())
DataType.UBYTE, DataType.BYTE -> assignConstantByte(assign.target, num.toInt())
DataType.UWORD, DataType.WORD -> assignConstantWord(assign.target, num.toInt())
DataType.FLOAT -> assignConstantFloat(assign.target, num.toDouble())
DataType.FLOAT -> assignConstantFloat(assign.target, num)
else -> throw AssemblyError("weird numval type")
}
}
@@ -128,7 +135,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val value = assign.source.memory!!
when (value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
val address = (value.addressExpression as NumericLiteralValue).number.toUInt()
assignMemoryByte(assign.target, address, null)
}
is IdentifierReference -> {
@@ -159,7 +166,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when (val sub = value.target.targetStatement(program)) {
is Subroutine -> {
asmgen.saveXbeforeCall(value)
asmgen.translateFunctionCall(value)
asmgen.translateFunctionCall(value, true)
val returnValue = sub.returntypes.zip(sub.asmReturnvaluesRegisters).singleOrNull { it.second.registerOrPair!=null } ?:
sub.returntypes.zip(sub.asmReturnvaluesRegisters).single { it.second.statusflag!=null }
when (returnValue.first) {
@@ -258,11 +265,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
assign.target,
false, program.memsizer, assign.position
))
val target = virtualRegsToVariables(assign.target)
when(value.operator) {
"+" -> {}
"-" -> augmentableAsmGen.inplaceNegate(assign.target, assign.target.datatype)
"~" -> augmentableAsmGen.inplaceInvert(assign.target, assign.target.datatype)
"not" -> augmentableAsmGen.inplaceBooleanNot(assign.target, assign.target.datatype)
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype)
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype)
"not" -> augmentableAsmGen.inplaceBooleanNot(target, target.datatype)
else -> throw AssemblyError("invalid prefix operator")
}
}
@@ -270,7 +278,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
// Everything else just evaluate via the stack.
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
// because the code here is the implementation of exactly that...)
// TODO FIX THIS... by using a temp var? so that it becomes augmentable assignment expression?
// TODO DON'T STACK-EVAL THIS... by using a temp var? so that it becomes augmentable assignment expression?
asmgen.translateExpression(value)
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
asmgen.signExtendStackLsb(assign.source.datatype)
@@ -342,7 +350,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when (value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
val address = (value.addressExpression as NumericLiteralValue).number.toUInt()
assignMemoryByteIntoWord(target, address, null)
return
}
@@ -802,7 +810,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if(target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype).toUInt()
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
@@ -1012,7 +1020,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
TargetStorageKind.ARRAY -> {
target.array!!
if(target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype).toUInt()
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
@@ -1067,7 +1075,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
adc #<${target.asmVarname}
bcc +
iny
+ jsr floats.copy_float""")
+ jsr floats.copy_float""")
}
else -> throw AssemblyError("weird dt")
}
@@ -1101,6 +1109,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
internal fun assignFAC2float(target: AsmAssignTarget) {
asmgen.out(" jsr floats.MOVFA") // fac2 -> fac1
assignFAC1float(target)
}
internal fun assignFAC1float(target: AsmAssignTarget) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@@ -1236,7 +1249,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if (target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype).toUInt()
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
}
else {
@@ -1289,7 +1302,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if (wordtarget.constArrayIndexValue!=null) {
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
val scaledIdx = wordtarget.constArrayIndexValue!! * 2u
asmgen.out(" lda $sourceName")
asmgen.signExtendAYlsb(DataType.BYTE)
asmgen.out(" sta ${wordtarget.asmVarname}+$scaledIdx | sty ${wordtarget.asmVarname}+$scaledIdx+1")
@@ -1357,7 +1370,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if (wordtarget.constArrayIndexValue!=null) {
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
val scaledIdx = wordtarget.constArrayIndexValue!! * 2u
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${wordtarget.asmVarname}+$scaledIdx+1")
@@ -1517,7 +1530,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if (target.constArrayIndexValue!=null) {
val idx = target.constArrayIndexValue!! * 2
val idx = target.constArrayIndexValue!! * 2u
when (regs) {
RegisterOrPair.AX -> asmgen.out(" sta ${target.asmVarname}+$idx | stx ${target.asmVarname}+$idx+1")
RegisterOrPair.AY -> asmgen.out(" sta ${target.asmVarname}+$idx | sty ${target.asmVarname}+$idx+1")
@@ -1738,8 +1751,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun assignConstantByte(target: AsmAssignTarget, byte: Short) {
if(byte==0.toShort() && asmgen.isTargetCpu(CpuType.CPU65c02)) {
private fun assignConstantByte(target: AsmAssignTarget, byte: Int) {
if(byte==0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
// optimize setting zero value for this cpu
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@@ -1972,7 +1985,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun assignMemoryByte(target: AsmAssignTarget, address: Int?, identifier: IdentifierReference?) {
private fun assignMemoryByte(target: AsmAssignTarget, address: UInt?, identifier: IdentifierReference?) {
if (address != null) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@@ -2056,7 +2069,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun assignMemoryByteIntoWord(wordtarget: AsmAssignTarget, address: Int?, identifier: IdentifierReference?) {
private fun assignMemoryByteIntoWord(wordtarget: AsmAssignTarget, address: UInt?, identifier: IdentifierReference?) {
if (address != null) {
when(wordtarget.kind) {
TargetStorageKind.VARIABLE -> {
@@ -2148,7 +2161,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
fun storeAIntoPointerVar(pointervar: IdentifierReference) {
val sourceName = asmgen.asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program)!!
val scopedName = vardecl.makeScopedName(vardecl.name)
val scopedName = vardecl.scopedName.joinToString(".")
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
if (asmgen.isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
@@ -9,6 +9,7 @@ import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compilerinterface.CpuType
internal class AugmentableAssignmentAsmGen(private val program: Program,
private val assignmentAsmGen: AssignmentAsmGen,
private val asmgen: AsmGen
@@ -20,15 +21,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when (val value = assign.source.expression!!) {
is PrefixExpression -> {
// A = -A , A = +A, A = ~A, A = not A
val target = assignmentAsmGen.virtualRegsToVariables(assign.target)
val itype = value.inferType(program)
if(!itype.isKnown)
throw AssemblyError("unknown dt")
val type = itype.getOr(DataType.UNDEFINED)
when (value.operator) {
"+" -> {}
"-" -> inplaceNegate(assign.target, type)
"~" -> inplaceInvert(assign.target, type)
"not" -> inplaceBooleanNot(assign.target, type)
"-" -> inplaceNegate(target, type)
"~" -> inplaceInvert(target, type)
"not" -> inplaceBooleanNot(target, type)
else -> throw AssemblyError("invalid prefix operator")
}
}
@@ -100,12 +102,54 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
throw FatalAstException("assignment should be augmentable $binExpr")
val leftBinExpr = binExpr.left as? BinaryExpression
val rightBinExpr = binExpr.right as? BinaryExpression
if(leftBinExpr!=null && rightBinExpr==null) {
if(leftBinExpr.left isSameAs astTarget) {
// X = (X <oper> Right) <oper> Something
inplaceModification(target, leftBinExpr.operator, leftBinExpr.right)
inplaceModification(target, binExpr.operator, binExpr.right)
return
}
if(leftBinExpr.right isSameAs astTarget) {
// X = (Left <oper> X) <oper> Something
if(leftBinExpr.operator in associativeOperators) {
inplaceModification(target, leftBinExpr.operator, leftBinExpr.left)
inplaceModification(target, binExpr.operator, binExpr.right)
return
} else {
throw AssemblyError("operands in wrong order for non-associative operator")
}
}
}
if(leftBinExpr==null && rightBinExpr!=null) {
if(rightBinExpr.left isSameAs astTarget) {
// X = Something <oper> (X <oper> Right)
if(binExpr.operator in associativeOperators) {
inplaceModification(target, rightBinExpr.operator, rightBinExpr.right)
inplaceModification(target, binExpr.operator, binExpr.left)
return
} else {
throw AssemblyError("operands in wrong order for non-associative operator")
}
}
if(rightBinExpr.right isSameAs astTarget) {
// X = Something <oper> (Left <oper> X)
if(binExpr.operator in associativeOperators && rightBinExpr.operator in associativeOperators) {
inplaceModification(target, rightBinExpr.operator, rightBinExpr.left)
inplaceModification(target, binExpr.operator, binExpr.left)
return
} else {
throw AssemblyError("operands in wrong order for non-associative operator")
}
}
}
throw FatalAstException("assignment should follow augmentable rules $binExpr")
}
private fun inplaceModification(target: AsmAssignTarget, operator: String, origValue: Expression) {
// the asm-gen code can deal with situations where you want to assign a byte into a word.
// it will create the most optimized code to do this (so it type-extends for us).
// But we can't deal with writing a word into a byte - explicit typeconversion is required
@@ -193,9 +237,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
else -> {
// TODO OTHER EVALUATION HERE, don't use the estack
// TODO OTHER EVALUATION HERE, don't use the estack to transfer the address to read/write from
asmgen.assignExpressionTo(memory.addressExpression, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, memory.definingSubroutine))
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1") // TODO don't use estack to transfer the address to read from
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
when {
valueLv != null -> inplaceModification_byte_litval_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, ident)
@@ -206,7 +250,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
else -> inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value)
}
asmgen.out(" lda P8ZP_SCRATCH_B1 | jsr prog8_lib.write_byte_to_address_on_stack | inx") // TODO don't use estack to transfer the address to read from
asmgen.out(" lda P8ZP_SCRATCH_B1 | jsr prog8_lib.write_byte_to_address_on_stack | inx")
}
}
}
@@ -301,8 +345,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
}
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg in-place modification")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack in-place modification")
TargetStorageKind.REGISTER -> throw AssemblyError("no asm gen for reg in-place modification")
TargetStorageKind.STACK -> throw AssemblyError("no asm gen for stack in-place modification")
}
}
@@ -1115,7 +1159,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sta $name
lda P8ZP_SCRATCH_W2+1
sta $name+1
""") }
""")
}
"<<" -> {
asmgen.out("""
ldy $otherName
@@ -1802,8 +1847,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
else -> throw AssemblyError("invalid reg dt for byte not")
}
}
TargetStorageKind.STACK -> TODO("missing codegen for byte stack not")
else -> throw AssemblyError("missing codegen for in-place not of ubyte ${target.kind}")
TargetStorageKind.STACK -> TODO("no asm gen for byte stack not")
else -> throw AssemblyError("no asm gen for in-place not of ubyte ${target.kind}")
}
}
DataType.UWORD -> {
@@ -1855,13 +1900,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
+ ldx #1
+""")
}
in Cx16VirtualRegisters -> TODO()
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
else -> throw AssemblyError("invalid reg dt for word not")
}
}
TargetStorageKind.MEMORY -> TODO("no asm gen for uword-memory not")
TargetStorageKind.STACK -> TODO("missing codegen for word stack not")
else -> throw AssemblyError("missing codegen for in-place not of uword for ${target.kind}")
TargetStorageKind.STACK -> TODO("no asm gen for word stack not")
else -> throw AssemblyError("no asm gen for in-place not of uword for ${target.kind}")
}
}
else -> throw AssemblyError("boolean-not of invalid type")
@@ -1911,8 +1955,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
else -> throw AssemblyError("invalid reg dt for byte invert")
}
}
TargetStorageKind.STACK -> TODO("missing codegen for byte stack invert")
else -> throw AssemblyError("missing codegen for in-place invert ubyte for ${target.kind}")
TargetStorageKind.STACK -> TODO("no asm gen for byte stack invert")
else -> throw AssemblyError("no asm gen for in-place invert ubyte for ${target.kind}")
}
}
DataType.UWORD -> {
@@ -1931,15 +1975,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
RegisterOrPair.AX -> asmgen.out(" pha | txa | eor #255 | tax | pla | eor #255")
RegisterOrPair.AY -> asmgen.out(" pha | tya | eor #255 | tay | pla | eor #255")
RegisterOrPair.XY -> asmgen.out(" txa | eor #255 | tax | tya | eor #255 | tay")
in Cx16VirtualRegisters -> {
TODO("codegen for cx16 word register invert")
}
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
else -> throw AssemblyError("invalid reg dt for word invert")
}
}
TargetStorageKind.MEMORY -> TODO("no asm gen for uword-memory invert")
TargetStorageKind.STACK -> TODO("missing codegen for word stack invert")
else -> throw AssemblyError("missing codegen for in-place invert uword for ${target.kind}")
TargetStorageKind.STACK -> TODO("no asm gen for word stack invert")
else -> throw AssemblyError("no asm gen for in-place invert uword for ${target.kind}")
}
}
else -> throw AssemblyError("invert of invalid type")
@@ -1959,15 +2000,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.A -> asmgen.out(" sta P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1")
RegisterOrPair.X -> asmgen.out(" stx P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1 | tax")
RegisterOrPair.Y -> asmgen.out(" sty P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1 | tay")
RegisterOrPair.A -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" eor #255 | ina")
else
asmgen.out(" eor #255 | clc | adc #1")
}
RegisterOrPair.X -> asmgen.out(" txa | eor #255 | tax | inx")
RegisterOrPair.Y -> asmgen.out(" tya | eor #255 | tay | iny")
else -> throw AssemblyError("invalid reg dt for byte negate")
}
}
TargetStorageKind.MEMORY -> TODO("can't in-place negate memory ubyte")
TargetStorageKind.STACK -> TODO("missing codegen for byte stack negate")
else -> throw AssemblyError("missing codegen for in-place negate byte array")
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't in-place negate")
TargetStorageKind.STACK -> TODO("no asm gen for byte stack negate")
else -> throw AssemblyError("no asm gen for in-place negate byte")
}
}
DataType.WORD -> {
@@ -2022,15 +2069,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sbc P8ZP_SCRATCH_REG+1
tay""")
}
in Cx16VirtualRegisters -> {
TODO("codegen for cx16 word register negate")
}
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
else -> throw AssemblyError("invalid reg dt for word neg")
}
}
TargetStorageKind.MEMORY -> TODO("no asm gen for word memory negate")
TargetStorageKind.STACK -> TODO("missing codegen for word stack negate")
else -> throw AssemblyError("missing codegen for in-place negate word array")
TargetStorageKind.STACK -> TODO("no asm gen for word stack negate")
else -> throw AssemblyError("no asm gen for in-place negate word")
}
}
DataType.FLOAT -> {
@@ -2043,13 +2087,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sta ${target.asmVarname}+1
""")
}
TargetStorageKind.REGISTER -> TODO("missing codegen for float reg negate")
TargetStorageKind.MEMORY -> TODO("missing codegen for float memory negate")
TargetStorageKind.STACK -> TODO("missing codegen for stack float negate")
TargetStorageKind.STACK -> TODO("no asm gen for stack float negate")
else -> throw AssemblyError("weird target kind for inplace negate float ${target.kind}")
}
}
else -> throw AssemblyError("negate of invalid type")
else -> throw AssemblyError("negate of invalid type $dt")
}
}
@@ -16,12 +16,12 @@ object CX16MachineDefinition: IMachineDefinition {
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
override val FLOAT_MEM_SIZE = 5
override val POINTER_MEM_SIZE = 2
override val BASIC_LOAD_ADDRESS = 0x0801
override val RAW_LOAD_ADDRESS = 0x8000
override val BASIC_LOAD_ADDRESS = 0x0801u
override val RAW_LOAD_ADDRESS = 0x8000u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override val ESTACK_LO = 0x0400 // $0400-$04ff inclusive
override val ESTACK_HI = 0x0500 // $0500-$05ff inclusive
override val ESTACK_LO = 0x0400u // $0400-$04ff inclusive
override val ESTACK_HI = 0x0500u // $0500-$05ff inclusive
override lateinit var zeropage: Zeropage
@@ -67,7 +67,7 @@ object CX16MachineDefinition: IMachineDefinition {
}
}
override fun isRegularRAMaddress(address: Int): Boolean = address < 0x9f00 || address in 0xa000..0xbfff
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = CX16Zeropage(compilerOptions)
@@ -89,10 +89,10 @@ object CX16MachineDefinition: IMachineDefinition {
class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x7a // temp storage for a single byte
override val SCRATCH_REG = 0x7b // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0x7c // temp storage 1 for a word $7c+$7d
override val SCRATCH_W2 = 0x7e // temp storage 2 for a word $7e+$7f
override val SCRATCH_B1 = 0x7au // temp storage for a single byte
override val SCRATCH_REG = 0x7bu // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0x7cu // temp storage 1 for a word $7c+$7d
override val SCRATCH_W2 = 0x7eu // temp storage 2 for a word $7e+$7f
init {
@@ -103,14 +103,14 @@ object CX16MachineDefinition: IMachineDefinition {
when (options.zeropage) {
ZeropageType.FULL -> {
free.addAll(0x22..0xff)
free.addAll(0x22u..0xffu)
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x22..0x7f)
free.addAll(0xa9..0xff)
free.addAll(0x22u..0x7fu)
free.addAll(0xa9u..0xffu)
}
ZeropageType.BASICSAFE -> {
free.addAll(0x22..0x7f)
free.addAll(0x22u..0x7fu)
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
+3 -4
View File
@@ -46,8 +46,8 @@ class AsmGenSymbolsTests: StringSpec({
}
*/
val varInSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "localvar", NumericLiteralValue.optimalInteger(1234, Position.DUMMY), false, false, false, Position.DUMMY)
val var2InSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", null, false, false, false, Position.DUMMY)
val varInSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "localvar", NumericLiteralValue.optimalInteger(1234, Position.DUMMY), false, false, false, null, Position.DUMMY)
val var2InSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", null, false, false, false, null, Position.DUMMY)
val labelInSub = Label("locallabel", Position.DUMMY)
val tgt = AssignTarget(IdentifierReference(listOf("tgt"), Position.DUMMY), null, null, Position.DUMMY)
@@ -63,13 +63,12 @@ class AsmGenSymbolsTests: StringSpec({
val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8)
val subroutine = Subroutine("start", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, statements, Position.DUMMY)
val labelInBlock = Label("label_outside", Position.DUMMY)
val varInBlock = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, false, Position.DUMMY)
val varInBlock = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, false, null, Position.DUMMY)
val block = Block("main", null, mutableListOf(labelInBlock, varInBlock, subroutine), false, Position.DUMMY)
val module = Module(mutableListOf(block), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
return program
}
+3 -3
View File
@@ -23,15 +23,15 @@ internal val DummyFunctions = object : IBuiltinFunctions {
}
internal val DummyMemsizer = object : IMemSizer {
override fun memorySize(dt: DataType): Int = 0
override fun memorySize(dt: DataType) = 0
}
internal val DummyStringEncoder = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
return emptyList()
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
return ""
}
}
@@ -15,7 +15,7 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.isInRegularRAMof
import prog8.compilerinterface.isIOAddress
class BinExprSplitter(private val program: Program, private val options: CompilationOptions, private val compTarget: ICompilationTarget) : AstWalker() {
@@ -46,10 +46,34 @@ X = BinExpr X = LeftExpr
*/
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target)) {
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
if(assignment.target isSameAs binExpr.right)
return noModifications
if(assignment.target isSameAs binExpr.left) {
if(binExpr.right.isSimple)
return noModifications
val leftBx = binExpr.left as? BinaryExpression
if(leftBx!=null && (!leftBx.left.isSimple || !leftBx.right.isSimple))
return noModifications
val rightBx = binExpr.right as? BinaryExpression
if(rightBx!=null && (!rightBx.left.isSimple || !rightBx.right.isSimple))
return noModifications
if(binExpr.right.isSimple && !assignment.isAugmentable) {
// TODO below attempts to remove stack-based evaluated expressions, but often the resulting code is BIGGER, and SLOWER.
// val dt = assignment.target.inferType(program)
// if(!dt.isInteger)
// return noModifications
// val tempVar = IdentifierReference(getTempVarName(dt), binExpr.right.position)
// val assignTempVar = Assignment(
// AssignTarget(tempVar, null, null, binExpr.right.position),
// binExpr.right, binExpr.right.position
// )
// return listOf(
// IAstModification.InsertBefore(assignment, assignTempVar, assignment.parent as IStatementContainer),
// IAstModification.ReplaceNode(binExpr.right, tempVar.copy(), binExpr)
// )
}
if(binExpr.right.isSimple) {
val firstAssign = Assignment(assignment.target.copy(), binExpr.left, binExpr.left.position)
val targetExpr = assignment.target.toExpression()
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
@@ -73,8 +97,7 @@ X = BinExpr X = LeftExpr
// we can see if we can unwrap the binary expression by working on a new temporary variable
// (that has the type of the expression), and then finally doing the typecast.
// Once it's outside the typecast, the regular splitting can commence.
val tempDt = origExpr.inferType(program).getOr(DataType.UNDEFINED)
val tempVar = when(tempDt) {
val tempVar = when(val tempDt = origExpr.inferType(program).getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> listOf("prog8_lib", "retval_interm_ub")
DataType.BYTE -> listOf("prog8_lib", "retval_interm_b")
DataType.UWORD -> listOf("prog8_lib", "retval_interm_uw")
@@ -98,7 +121,7 @@ X = BinExpr X = LeftExpr
private fun isSimpleTarget(target: AssignTarget) =
if (target.identifier!=null || target.memoryAddress!=null)
target.isInRegularRAMof(compTarget.machine)
!target.isIOAddress(compTarget.machine)
else
false
@@ -46,14 +46,14 @@ class ConstExprEvaluator {
left.number.toInt().ushr(amount.number.toInt())
else
left.number.toInt().shr(amount.number.toInt())
return NumericLiteralValue(left.type, result, left.position)
return NumericLiteralValue(left.type, result.toDouble(), left.position)
}
private fun shiftedleft(left: NumericLiteralValue, amount: NumericLiteralValue): Expression {
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
throw ExpressionError("cannot compute $left << $amount", left.position)
val result = left.number.toInt().shl(amount.number.toInt())
return NumericLiteralValue(left.type, result, left.position)
return NumericLiteralValue(left.type, result.toDouble(), left.position)
}
private fun logicalxor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
@@ -61,12 +61,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toDouble() != 0.0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number != 0.0), left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toInt() != 0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toDouble() != 0.0), left.position)
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number != 0.0) xor (right.number.toInt() != 0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number != 0.0) xor (right.number != 0.0), left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@@ -78,12 +78,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toDouble() != 0.0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toDouble() != 0.0, left.position)
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number != 0.0 || right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number != 0.0 || right.number != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@@ -95,12 +95,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toDouble() != 0.0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toDouble() != 0.0, left.position)
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number != 0.0 && right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number != 0.0 && right.number != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@@ -110,11 +110,11 @@ class ConstExprEvaluator {
private fun bitwisexor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toShort(), left.position)
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, left.number.toInt() xor right.number.toInt(), left.position)
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left ^ $right", left.position)
@@ -123,11 +123,11 @@ class ConstExprEvaluator {
private fun bitwiseor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position)
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), left.position)
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() or right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left | $right", left.position)
@@ -136,11 +136,11 @@ class ConstExprEvaluator {
private fun bitwiseand(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toShort(), left.position)
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toDouble(), left.position)
}
} else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, left.number.toInt() and right.number.toInt(), left.position)
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() and right.number.toInt()).toDouble(), left.position)
}
}
throw ExpressionError("cannot calculate $left & $right", left.position)
@@ -151,12 +151,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number.toDouble()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number), left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toInt()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toDouble()), left.position)
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.pow(right.number.toInt()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.pow(right.number), left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@@ -168,12 +168,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toDouble(), left.position)
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number + right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@@ -185,12 +185,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toDouble(), left.position)
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number - right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@@ -202,12 +202,12 @@ class ConstExprEvaluator {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toDouble(), left.position)
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number * right.number, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@@ -227,19 +227,19 @@ class ConstExprEvaluator {
NumericLiteralValue.optimalInteger(result, left.position)
}
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number.toDouble(), left.position)
if(right.number==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number, left.position)
}
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toInt(), left.position)
NumericLiteralValue(DataType.FLOAT, left.number / right.number.toInt(), left.position)
}
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toDouble(), left.position)
if(right.number ==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number / right.number, left.position)
}
else -> throw ExpressionError(error, left.position)
}
@@ -256,19 +256,19 @@ class ConstExprEvaluator {
NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
}
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number.toDouble(), left.position)
if(right.number ==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number, left.position)
}
else -> throw ExpressionError(error, left.position)
}
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toInt(), left.position)
NumericLiteralValue(DataType.FLOAT, left.number % right.number.toInt(), left.position)
}
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toDouble(), left.position)
if(right.number ==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number % right.number, left.position)
}
else -> throw ExpressionError(error, left.position)
}
@@ -40,7 +40,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
}
DataType.FLOAT -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position),
NumericLiteralValue(DataType.FLOAT, -subexpr.number, subexpr.position),
parent))
}
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
@@ -48,29 +48,29 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
"~" -> when (subexpr.type) {
DataType.BYTE -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv(), subexpr.position),
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv().toDouble(), subexpr.position),
parent))
}
DataType.UBYTE -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.UBYTE, subexpr.number.toInt().inv() and 255, subexpr.position),
NumericLiteralValue(DataType.UBYTE, (subexpr.number.toInt().inv() and 255).toDouble(), subexpr.position),
parent))
}
DataType.WORD -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv(), subexpr.position),
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv().toDouble(), subexpr.position),
parent))
}
DataType.UWORD -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.UWORD, subexpr.number.toInt().inv() and 65535, subexpr.position),
NumericLiteralValue(DataType.UWORD, (subexpr.number.toInt().inv() and 65535).toDouble(), subexpr.position),
parent))
}
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
}
"not" -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position),
NumericLiteralValue.fromBoolean(subexpr.number == 0.0, subexpr.position),
parent))
}
else -> throw ExpressionError(expr.operator, subexpr.position)
@@ -79,7 +79,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
return noModifications
}
/**
/*
* Try to constfold a binary expression.
* Compile-time constant sub expressions will be evaluated on the spot.
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
@@ -101,23 +101,50 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
val rightconst = expr.right.constValue(program)
val modifications = mutableListOf<IAstModification>()
if(expr.operator=="==" && rightconst!=null) {
val leftExpr = expr.left as? BinaryExpression
if(leftExpr!=null) {
val leftRightConst = leftExpr.right.constValue(program)
if(leftRightConst!=null) {
when (leftExpr.operator) {
"+" -> {
// X + С1 == C2 --> X == C2 - C1
val newRightConst = NumericLiteralValue(rightconst.type, rightconst.number - leftRightConst.number, rightconst.position)
return listOf(
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
IAstModification.ReplaceNode(expr.right, newRightConst, expr)
)
}
"-" -> {
// X - С1 == C2 --> X == C2 + C1
val newRightConst = NumericLiteralValue(rightconst.type, rightconst.number + leftRightConst.number, rightconst.position)
return listOf(
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
IAstModification.ReplaceNode(expr.right, newRightConst, expr)
)
}
}
}
}
}
if(expr.operator == "**" && leftconst!=null) {
// optimize various simple cases of ** :
// optimize away 1 ** x into just 1 and 0 ** x into just 0
// optimize 2 ** x into (1<<x) if both operands are integer.
val leftDt = leftconst.inferType(program).getOr(DataType.UNDEFINED)
when (leftconst.number.toDouble()) {
when (leftconst.number) {
0.0 -> {
val value = NumericLiteralValue(leftDt, 0, expr.position)
val value = NumericLiteralValue(leftDt, 0.0, expr.position)
modifications += IAstModification.ReplaceNode(expr, value, parent)
}
1.0 -> {
val value = NumericLiteralValue(leftDt, 1, expr.position)
val value = NumericLiteralValue(leftDt, 1.0, expr.position)
modifications += IAstModification.ReplaceNode(expr, value, parent)
}
2.0 -> {
if(rightconst!=null) {
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number.toDouble()), expr.position)
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number), expr.position)
modifications += IAstModification.ReplaceNode(expr, value, parent)
} else {
val rightDt = expr.right.inferType(program).getOr(DataType.UNDEFINED)
@@ -128,7 +155,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
is VarDecl -> parent.datatype
else -> leftDt
}
val one = NumericLiteralValue(targetDt, 1, expr.position)
val one = NumericLiteralValue(targetDt, 1.0, expr.position)
val shift = BinaryExpression(one, "<<", expr.right, expr.position)
modifications += IAstModification.ReplaceNode(expr, shift, parent)
}
@@ -159,13 +186,52 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
}
}
val evaluator = ConstExprEvaluator()
// const fold when both operands are a const
if(leftconst != null && rightconst != null) {
val evaluator = ConstExprEvaluator()
val result = evaluator.evaluate(leftconst, expr.operator, rightconst)
modifications += IAstModification.ReplaceNode(expr, result, parent)
}
val leftBinExpr = expr.left as? BinaryExpression
val rightBinExpr = expr.right as? BinaryExpression
if(expr.operator=="+" || expr.operator=="-") {
if(leftBinExpr!=null && rightBinExpr!=null) {
val c1 = leftBinExpr.right.constValue(program)
val c2 = rightBinExpr.right.constValue(program)
if(leftBinExpr.operator=="+" && rightBinExpr.operator=="+") {
if (c1 != null && c2 != null) {
// (X + C1) <plusmin> (Y + C2) => (X <plusmin> Y) + (C1 <plusmin> C2)
val c3 = evaluator.evaluate(c1, expr.operator, c2)
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
val newExpr = BinaryExpression(xwithy, "+", c3, expr.position)
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
}
}
else if(leftBinExpr.operator=="-" && rightBinExpr.operator=="-") {
if (c1 != null && c2 != null) {
// (X - C1) <plusmin> (Y - C2) => (X <plusmin> Y) - (C1 <plusmin> C2)
val c3 = evaluator.evaluate(c1, expr.operator, c2)
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
val newExpr = BinaryExpression(xwithy, "-", c3, expr.position)
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
}
}
else if(leftBinExpr.operator=="*" && rightBinExpr.operator=="*"){
if (c1 != null && c2 != null && c1==c2) {
//(X * C) <plusmin> (Y * C) => (X <plusmin> Y) * C
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
val newExpr = BinaryExpression(xwithy, "*", c1, expr.position)
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
}
}
}
}
return modifications
}
@@ -24,11 +24,16 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
try {
val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
&& declConstValue.inferType(program) isnot decl.datatype) {
// cast the numeric literal to the appropriate datatype of the variable
val cast = declConstValue.cast(decl.datatype)
if(cast.isValid)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
&& declConstValue.type != decl.datatype) {
// avoid silent float roundings
if(decl.datatype in IntegerDatatypes && declConstValue.type==DataType.FLOAT) {
errors.err("refused silent rounding of float to avoid loss of precision", decl.value!!.position)
} else {
// cast the numeric literal to the appropriate datatype of the variable
val cast = declConstValue.cast(decl.datatype)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
}
}
} catch (x: UndefinedSymbolError) {
errors.err(x.message, x.position)
@@ -38,9 +43,9 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
}
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> {
val from = range.from.constValue(program)?.number?.toDouble()
val to = range.to.constValue(program)?.number?.toDouble()
val step = range.step.constValue(program)?.number?.toDouble()
val from = range.from.constValue(program)?.number
val to = range.to.constValue(program)?.number
val step = range.step.constValue(program)?.number
if(from==null) {
if(!range.from.inferType(program).isInteger)
@@ -105,8 +110,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// the initializer value can't refer to the variable itself (recursive definition)
// TODO: use call graph for this?
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true) {
if(decl.value?.referencesIdentifier(listOf(decl.name)) == true || decl.arraysize?.indexExpr?.referencesIdentifier(listOf(decl.name)) == true) {
errors.err("recursive var declaration", decl.position)
return noModifications
}
@@ -132,7 +136,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
// vardecl: for scalar float vars, promote constant integer initialization values to floats
val litval = decl.value as? NumericLiteralValue
if (litval!=null && litval.type in IntegerDatatypes) {
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number, litval.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
@@ -142,17 +146,17 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
// convert the initializer range expression to an actual array
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
val newValue = if(eltType in ByteDatatypes) {
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
constRange.map { NumericLiteralValue(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
constRange.map { NumericLiteralValue(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
@@ -185,7 +189,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayToElementTypes.getValue(decl.datatype), it, numericLv.position) }.toTypedArray<Expression>()
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
@@ -210,7 +214,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
val size = decl.arraysize?.constIndex() ?: return noModifications
if(rangeExpr==null && numericLv!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = numericLv.number.toDouble()
val fillvalue = numericLv.number
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", numericLv.position)
else {
@@ -19,17 +19,6 @@ import kotlin.math.pow
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
*(&X) => X
X % 1 => 0
X / 1 => X
X ^ -1 => ~x
X >= 1 => X > 0
X < 1 => X <= 0
X + С1 == C2 => X == C2 - C1
((X + C1) + C2) => (X + (C1 + C2))
((X + C1) + (Y + C2)) => ((X + Y) + (C1 + C2))
*/
@@ -160,7 +149,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val x = expr.right
val y = determineY(x, leftBinExpr)
if (y != null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1, y.position), y.position)
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1.0, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
@@ -170,7 +159,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val x = expr.right
val y = determineY(x, leftBinExpr)
if (y != null) {
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1, y.position), y.position)
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1.0, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yMinus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
@@ -190,14 +179,26 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
}
}
if(expr.operator == ">=" && rightVal?.number == 0) {
if(leftDt!=DataType.FLOAT && expr.operator == ">=" && rightVal?.number == 1.0) {
// for integers: x >= 1 --> x > 0
expr.operator = ">"
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteralValue.optimalInteger(0, expr.right.position), expr))
}
if(expr.operator == ">=" && rightVal?.number == 0.0) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
// unsigned >= 0 --> true
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
}
}
if(expr.operator == "<" && rightVal?.number == 0) {
if(leftDt!=DataType.FLOAT && expr.operator == "<" && rightVal?.number == 1.0) {
// for integers: x < 1 --> x <= 0
expr.operator = "<="
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteralValue.optimalInteger(0, expr.right.position), expr))
}
if(expr.operator == "<" && rightVal?.number == 0.0) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
// unsigned < 0 --> false
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent))
@@ -231,52 +232,62 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
val newExpr: Expression? = when (expr.operator) {
"or" -> {
if ((leftVal != null && leftVal.asBooleanValue) || (rightVal != null && rightVal.asBooleanValue))
constTrue
else if (leftVal != null && !leftVal.asBooleanValue)
expr.right
else if (rightVal != null && !rightVal.asBooleanValue)
expr.left
else
null
when {
leftVal != null && leftVal.asBooleanValue || rightVal != null && rightVal.asBooleanValue -> constTrue
leftVal != null && !leftVal.asBooleanValue -> expr.right
rightVal != null && !rightVal.asBooleanValue -> expr.left
else -> null
}
}
"and" -> {
if ((leftVal != null && !leftVal.asBooleanValue) || (rightVal != null && !rightVal.asBooleanValue))
constFalse
else if (leftVal != null && leftVal.asBooleanValue)
expr.right
else if (rightVal != null && rightVal.asBooleanValue)
expr.left
else
null
when {
leftVal != null && !leftVal.asBooleanValue || rightVal != null && !rightVal.asBooleanValue -> constFalse
leftVal != null && leftVal.asBooleanValue -> expr.right
rightVal != null && rightVal.asBooleanValue -> expr.left
else -> null
}
}
"xor" -> {
if (leftVal != null && !leftVal.asBooleanValue)
expr.right
else if (rightVal != null && !rightVal.asBooleanValue)
expr.left
else if (leftVal != null && leftVal.asBooleanValue)
PrefixExpression("not", expr.right, expr.right.position)
else if (rightVal != null && rightVal.asBooleanValue)
PrefixExpression("not", expr.left, expr.left.position)
else
null
when {
leftVal != null && !leftVal.asBooleanValue -> expr.right
rightVal != null && !rightVal.asBooleanValue -> expr.left
leftVal != null && leftVal.asBooleanValue -> PrefixExpression("not", expr.right, expr.right.position)
rightVal != null && rightVal.asBooleanValue -> PrefixExpression("not", expr.left, expr.left.position)
else -> null
}
}
"|", "^" -> {
if (leftVal != null && !leftVal.asBooleanValue)
expr.right
else if (rightVal != null && !rightVal.asBooleanValue)
expr.left
else
null
"|" -> {
when {
leftVal?.number==0.0 -> expr.right
rightVal?.number==0.0 -> expr.left
rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteralValue(DataType.UBYTE, 255.0, rightVal.position)
rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteralValue(DataType.UWORD, 65535.0, rightVal.position)
leftIDt.isBytes && leftVal?.number==255.0 -> NumericLiteralValue(DataType.UBYTE, 255.0, leftVal.position)
leftIDt.isWords && leftVal?.number==65535.0 -> NumericLiteralValue(DataType.UWORD, 65535.0, leftVal.position)
else -> null
}
}
"^" -> {
when {
leftVal?.number==0.0 -> expr.right
rightVal?.number==0.0 -> expr.left
rightIDt.isBytes && rightVal?.number==255.0 -> PrefixExpression("~", expr.left, expr.left.position)
rightIDt.isWords && rightVal?.number==65535.0 -> PrefixExpression("~", expr.left, expr.left.position)
leftIDt.isBytes && leftVal?.number==255.0 -> PrefixExpression("~", expr.right, expr.right.position)
leftIDt.isWords && leftVal?.number==65535.0 -> PrefixExpression("~", expr.right, expr.right.position)
else -> null
}
}
"&" -> {
if (leftVal != null && !leftVal.asBooleanValue)
constFalse
else if (rightVal != null && !rightVal.asBooleanValue)
constFalse
else
null
when {
leftVal?.number==0.0 -> constFalse
rightVal?.number==0.0 -> constFalse
rightIDt.isBytes && rightVal?.number==255.0 -> expr.left
rightIDt.isWords && rightVal?.number==65535.0 -> expr.left
leftIDt.isBytes && leftVal?.number==255.0 -> expr.right
leftIDt.isWords && leftVal?.number==65535.0 -> expr.right
else -> null
}
}
"*" -> optimizeMultiplication(expr, leftVal, rightVal)
"/" -> optimizeDivision(expr, leftVal, rightVal)
@@ -320,7 +331,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
// useless msb() of byte value that was typecasted to word, replace with 0
return listOf(IAstModification.ReplaceNode(
functionCall,
NumericLiteralValue(valueDt.getOr(DataType.UBYTE), 0, arg.expression.position),
NumericLiteralValue(valueDt.getOr(DataType.UBYTE), 0.0, arg.expression.position),
parent))
}
} else {
@@ -329,7 +340,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
// useless msb() of byte value, replace with 0
return listOf(IAstModification.ReplaceNode(
functionCall,
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0, arg.position),
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0.0, arg.position),
parent))
}
}
@@ -362,7 +373,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
if (rightVal2 != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal2
when (rightConst.number.toDouble()) {
when (rightConst.number) {
0.0 -> {
// left
return expr2.left
@@ -371,7 +382,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
}
// no need to check for left val constant (because of associativity)
val rnum = rightVal?.number?.toDouble()
val rnum = rightVal?.number
if(rnum!=null && rnum<0.0) {
expr.operator = "-"
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
@@ -392,7 +403,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
if (rightVal != null) {
// right value is a constant, see if we can optimize
val rnum = rightVal.number.toDouble()
val rnum = rightVal.number
if (rnum == 0.0) {
// left
return expr.left
@@ -406,7 +417,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
}
if (leftVal != null) {
// left value is a constant, see if we can optimize
when (leftVal.number.toDouble()) {
when (leftVal.number) {
0.0 -> {
// -right
return PrefixExpression("-", expr.right, expr.position)
@@ -425,7 +436,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
if (rightVal != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
when (rightConst.number.toDouble()) {
when (rightConst.number) {
-3.0 -> {
// -1/(left*left*left)
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
@@ -445,7 +456,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
}
0.0 -> {
// 1
return NumericLiteralValue(rightConst.type, 1, expr.position)
return NumericLiteralValue(rightConst.type, 1.0, expr.position)
}
0.5 -> {
// sqrt(left)
@@ -467,18 +478,18 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
}
if (leftVal != null) {
// left value is a constant, see if we can optimize
when (leftVal.number.toDouble()) {
when (leftVal.number) {
-1.0 -> {
// -1
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
}
0.0 -> {
// 0
return NumericLiteralValue(leftVal.type, 0, expr.position)
return NumericLiteralValue(leftVal.type, 0.0, expr.position)
}
1.0 -> {
//1
return NumericLiteralValue(leftVal.type, 1, expr.position)
return NumericLiteralValue(leftVal.type, 1.0, expr.position)
}
}
@@ -500,7 +511,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val idt = expr.inferType(program)
if(!idt.isKnown)
throw FatalAstException("unknown dt")
return NumericLiteralValue(idt.getOr(DataType.UNDEFINED), 0, expr.position)
return NumericLiteralValue(idt.getOr(DataType.UNDEFINED), 0.0, expr.position)
} else if (cv in powersOfTwo) {
expr.operator = "&"
expr.right = NumericLiteralValue.optimalInteger(cv!!.toInt()-1, expr.position)
@@ -520,7 +531,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
if (rightVal != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
val cv = rightConst.number.toDouble()
val cv = rightConst.number
val leftIDt = expr.left.inferType(program)
if (!leftIDt.isKnown)
return null
@@ -555,22 +566,22 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
}
if (leftDt == DataType.UBYTE) {
if (abs(rightConst.number.toDouble()) >= 256.0) {
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
if (abs(rightConst.number) >= 256.0) {
return NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
}
} else if (leftDt == DataType.UWORD) {
if (abs(rightConst.number.toDouble()) >= 65536.0) {
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
if (abs(rightConst.number) >= 65536.0) {
return NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
}
}
}
if (leftVal != null) {
// left value is a constant, see if we can optimize
when (leftVal.number.toDouble()) {
when (leftVal.number) {
0.0 -> {
// 0
return NumericLiteralValue(leftVal.type, 0, expr.position)
return NumericLiteralValue(leftVal.type, 0.0, expr.position)
}
}
}
@@ -587,14 +598,14 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
// right value is a constant, see if we can optimize
val leftValue: Expression = expr2.left
val rightConst: NumericLiteralValue = rightVal2
when (val cv = rightConst.number.toDouble()) {
when (val cv = rightConst.number) {
-1.0 -> {
// -left
return PrefixExpression("-", leftValue, expr.position)
}
0.0 -> {
// 0
return NumericLiteralValue(rightConst.type, 0, expr.position)
return NumericLiteralValue(rightConst.type, 0.0, expr.position)
}
1.0 -> {
// left
@@ -635,12 +646,12 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE, DataType.BYTE -> {
if (amount >= 8) {
return NumericLiteralValue(targetDt, 0, expr.position)
return NumericLiteralValue(targetDt, 0.0, expr.position)
}
}
DataType.UWORD, DataType.WORD -> {
if (amount >= 16) {
return NumericLiteralValue(targetDt, 0, expr.position)
return NumericLiteralValue(targetDt, 0.0, expr.position)
} else if (amount >= 8) {
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
if (amount == 8) {
@@ -687,7 +698,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
if (amount == 8) {
// mkword(0, msb(v))
val zero = NumericLiteralValue(DataType.UBYTE, 0, expr.position)
val zero = NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(zero, msb), expr.position)
}
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)
@@ -2,6 +2,9 @@ package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.InferredTypes
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
@@ -65,3 +68,15 @@ fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICom
opti.visit(this)
return opti.applyModifications()
}
fun getTempVarName(dt: InferredTypes.InferredType): List<String> {
return when {
// TODO assume (hope) cx16.r9 isn't used for anything else...
dt.istype(DataType.UBYTE) -> listOf("cx16", "r9L")
dt.istype(DataType.BYTE) -> listOf("cx16", "r9sL")
dt.istype(DataType.UWORD) -> listOf("cx16", "r9")
dt.istype(DataType.WORD) -> listOf("cx16", "r9s")
dt.isPassByReference -> listOf("cx16", "r9")
else -> throw FatalAstException("invalid dt $dt")
}
}
@@ -1,9 +1,6 @@
package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
@@ -25,13 +22,19 @@ class StatementOptimizer(private val program: Program,
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// if the first instruction in the called subroutine is a return statement with a simple value,
// remove the jump altogeter and inline the returnvalue directly.
fun scopePrefix(variable: IdentifierReference): IdentifierReference {
val target = variable.targetStatement(program) as INamedStatement
return IdentifierReference(target.scopedName, variable.position)
}
val subroutine = functionCall.target.targetSubroutine(program)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return && first.value?.isSimple==true) {
val copy = when(val orig = first.value!!) {
is AddressOf -> {
val scoped = scopePrefix(orig.identifier, subroutine)
val scoped = scopePrefix(orig.identifier)
AddressOf(scoped, orig.position)
}
is DirectMemoryRead -> {
@@ -40,7 +43,7 @@ class StatementOptimizer(private val program: Program,
else -> return noModifications
}
}
is IdentifierReference -> scopePrefix(orig, subroutine)
is IdentifierReference -> scopePrefix(orig)
is NumericLiteralValue -> orig.copy()
is StringLiteralValue -> orig.copy()
else -> return noModifications
@@ -51,13 +54,10 @@ class StatementOptimizer(private val program: Program,
return noModifications
}
private fun scopePrefix(variable: IdentifierReference, subroutine: Subroutine): IdentifierReference {
val scoped = subroutine.makeScopedName(variable.nameInSource.last())
return IdentifierReference(scoped.split('.'), variable.position)
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in functions.names) {
if(functionCallStatement.target.targetStatement(program) is BuiltinFunctionStatementPlaceholder) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in functions.purefunctionNames) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
@@ -82,7 +82,7 @@ class StatementOptimizer(private val program: Program,
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
val chrout = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toDouble(), pos)),
functionCallStatement.void, pos
)
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
@@ -90,12 +90,12 @@ class StatementOptimizer(private val program: Program,
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toDouble(), pos)),
functionCallStatement.void, pos
)
val chrout2 = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toDouble(), pos)),
functionCallStatement.void, pos
)
return listOf(
@@ -115,23 +115,25 @@ class StatementOptimizer(private val program: Program,
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
}
// see if we can optimize any complex arguments
// TODO for now, only works for single-argument functions because we use just 1 temp var: R9
if(functionCallStatement.target.nameInSource !in listOf(listOf("pop"), listOf("popw")) && functionCallStatement.args.size==1) {
val arg = functionCallStatement.args[0]
if(!arg.isSimple && arg !is TypecastExpression && arg !is IFunctionCall) {
val name = getTempVarName(arg.inferType(program))
val tempvar = IdentifierReference(name, functionCallStatement.position)
val assignTempvar = Assignment(AssignTarget(tempvar.copy(), null, null, functionCallStatement.position), arg, functionCallStatement.position)
return listOf(
IAstModification.InsertBefore(functionCallStatement, assignTempvar, parent as IStatementContainer),
IAstModification.ReplaceNode(arg, tempvar, functionCallStatement)
)
}
}
return noModifications
}
// override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// // if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
// val subroutine = functionCall.target.targetSubroutine(program)
// if(subroutine!=null) {
// val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
// if(first is Return && first.value!=null) {
// val constval = first.value?.constValue(program)
// if(constval!=null)
// return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
// }
// }
// return noModifications
// }
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
// remove empty if statements
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isEmpty())
@@ -196,7 +198,7 @@ class StatementOptimizer(private val program: Program,
if(size==1) {
// loop over string of length 1 -> just assign the single character
val character = compTarget.encodeString(sv.value, sv.altEncoding)[0]
val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position)
val byte = NumericLiteralValue(DataType.UBYTE, character.toDouble(), iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
@@ -277,12 +279,13 @@ class StatementOptimizer(private val program: Program,
}
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
// if the jump is to the next statement, remove the jump
val scope = jump.parent as IStatementContainer
val label = jump.identifier?.targetStatement(program)
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
return listOf(IAstModification.Remove(jump, scope))
if(!jump.isGosub) {
// if the jump is to the next statement, remove the jump
val scope = jump.parent as IStatementContainer
val label = jump.identifier?.targetStatement(program)
if (label != null && scope.statements.indexOf(label) == scope.statements.indexOf(jump) + 1)
return listOf(IAstModification.Remove(jump, scope))
}
return noModifications
}
@@ -305,22 +308,22 @@ class StatementOptimizer(private val program: Program,
if(rNum!=null) {
if (op1 == "+" || op1 == "-") {
if (op2 == "+") {
// A = A +/- B + N
// A = A +/- B + N ---> A = A +/- B ; A = A + N
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
val addConstant = Assignment(
assignment.target,
BinaryExpression(binExpr.left, "+", rExpr.right, rExpr.position),
assignment.target.copy(),
BinaryExpression(binExpr.left.copy(), "+", rExpr.right, rExpr.position),
assignment.position
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, addConstant, parent as IStatementContainer))
} else if (op2 == "-") {
// A = A +/- B - N
// A = A +/- B - N ---> A = A +/- B ; A = A - N
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
val subConstant = Assignment(
assignment.target,
BinaryExpression(binExpr.left, "-", rExpr.right, rExpr.position),
assignment.target.copy(),
BinaryExpression(binExpr.left.copy(), "-", rExpr.right, rExpr.position),
assignment.position
)
return listOf(
@@ -355,14 +358,26 @@ class StatementOptimizer(private val program: Program,
throw FatalAstException("can't infer type of assignment target")
// optimize binary expressions a bit
val targetDt = targetIDt.getOr(DataType.UNDEFINED)
val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) {
val rightCv = bexpr.right.constValue(program)?.number?.toDouble()
val rightCv = bexpr.right.constValue(program)?.number
if(bexpr.operator=="-" && rightCv==null) {
if(bexpr.right isSameAs assignment.target) {
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation)
val negation = PrefixExpression("-", bexpr.right.copy(), bexpr.position)
val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), assignment.position)
return listOf(
IAstModification.ReplaceNode(bexpr, negation, assignment),
IAstModification.InsertAfter(assignment, addValue, parent as IStatementContainer)
)
}
}
if (rightCv != null && assignment.target isSameAs bexpr.left) {
// assignments of the form: X = X <operator> <expr>
// remove assignments that have no effect (such as X=X+0)
// optimize/rewrite some other expressions
val targetDt = targetIDt.getOr(DataType.UNDEFINED)
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program))?.type
when (bexpr.operator) {
"+" -> {
@@ -375,7 +390,7 @@ class StatementOptimizer(private val program: Program,
repeat(rightCv.toInt()) {
incs.statements.add(PostIncrDecr(assignment.target.copy(), "++", assignment.position))
}
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
listOf(IAstModification.ReplaceNode(assignment, if(incs.statements.size==1) incs.statements[0] else incs, parent))
}
}
}
@@ -1,18 +1,16 @@
package prog8.optimizer
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.PrefixExpression
import prog8.ast.expressions.TypecastExpression
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.CallGraph
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.isInRegularRAMof
import prog8.compilerinterface.isIOAddress
class UnusedCodeRemover(private val program: Program,
@@ -30,27 +28,28 @@ class UnusedCodeRemover(private val program: Program,
}
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
reportUnreachable(breakStmt, parent as IStatementContainer)
reportUnreachable(breakStmt)
return emptyList()
}
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
reportUnreachable(jump, parent as IStatementContainer)
if(!jump.isGosub)
reportUnreachable(jump)
return emptyList()
}
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
reportUnreachable(returnStmt, parent as IStatementContainer)
reportUnreachable(returnStmt)
return emptyList()
}
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.last() == "exit")
reportUnreachable(functionCallStatement, parent as IStatementContainer)
reportUnreachable(functionCallStatement)
return emptyList()
}
private fun reportUnreachable(stmt: Statement, parent: IStatementContainer) {
private fun reportUnreachable(stmt: Statement) {
when(val next = stmt.nextSibling()) {
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine -> {}
else -> errors.warn("unreachable code", next.position)
@@ -58,8 +57,7 @@ class UnusedCodeRemover(private val program: Program,
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val removeDoubleAssignments = deduplicateAssignments(scope.statements)
return removeDoubleAssignments.map { IAstModification.Remove(it, scope) }
return deduplicateAssignments(scope.statements, scope)
}
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
@@ -75,8 +73,7 @@ class UnusedCodeRemover(private val program: Program,
}
}
val removeDoubleAssignments = deduplicateAssignments(block.statements)
return removeDoubleAssignments.map { IAstModification.Remove(it, block) }
return deduplicateAssignments(block.statements, block)
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
@@ -99,8 +96,7 @@ class UnusedCodeRemover(private val program: Program,
}
}
val removeDoubleAssignments = deduplicateAssignments(subroutine.statements)
return removeDoubleAssignments.map { IAstModification.Remove(it, subroutine) }
return deduplicateAssignments(subroutine.statements, subroutine)
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
@@ -112,19 +108,14 @@ class UnusedCodeRemover(private val program: Program,
errors.warn("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
} else {
// if all usages are just an assignment to this vardecl
// if all usages are just an assignment to this vardecl,
// and it is in regular RAM, then remove the var as well including all assignments
val assignTargets = usages.mapNotNull {
if(it.parent is AssignTarget)
it.parent as AssignTarget
else if(it.parent.parent is AssignTarget)
it.parent.parent as AssignTarget
else null
it.parent as? AssignTarget
}.filter {
it.isInRegularRAMof(compTarget.machine)
!it.isIOAddress(compTarget.machine)
}
if(assignTargets.size==usages.size) {
// TODO FIX THAT A MEMREAD OF THE VARIABLE ISN'T RECOGNISED AS A USE (imageviewer iff_module.p8 pixptr)
errors.warn("removing unused variable '${decl.name}'", decl.position)
val assignmentsToRemove = assignTargets.map { it.parent to it.parent.parent as IStatementContainer}.toSet()
return assignmentsToRemove.map {
@@ -140,28 +131,117 @@ class UnusedCodeRemover(private val program: Program,
return noModifications
}
private fun deduplicateAssignments(statements: List<Statement>): List<Assignment> {
private fun deduplicateAssignments(statements: List<Statement>, scope: IStatementContainer): List<IAstModification> {
// removes 'duplicate' assignments that assign the same target directly after another
val linesToRemove = mutableListOf<Assignment>()
val modifications = mutableListOf<IAstModification>()
for (stmtPairs in statements.windowed(2, step = 1)) {
val assign1 = stmtPairs[0] as? Assignment
val assign2 = stmtPairs[1] as? Assignment
if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAMof(compTarget.machine)) {
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray())))
// only remove the second assignment if its value is a simple expression!
when(assign2.value) {
is PrefixExpression,
is BinaryExpression,
is TypecastExpression,
is FunctionCall -> { /* don't remove */ }
else -> linesToRemove.add(assign1)
}
fun substituteZeroInBinexpr(expr: BinaryExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
if(expr.left isSameAs assign2.target) {
// X = X <oper> Right
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
expr.left, zero, expr
))
}
if(expr.right isSameAs assign2.target) {
// X = Left <oper> X
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
expr.right, zero, expr
))
}
val leftBinExpr = expr.left as? BinaryExpression
val rightBinExpr = expr.right as? BinaryExpression
if(leftBinExpr!=null && rightBinExpr==null) {
if(leftBinExpr.left isSameAs assign2.target) {
// X = (X <oper> Right) <oper> Something
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
leftBinExpr.left, zero, leftBinExpr
))
}
if(leftBinExpr.right isSameAs assign2.target) {
// X = (Left <oper> X) <oper> Something
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
leftBinExpr.right, zero, leftBinExpr
))
}
}
if(leftBinExpr==null && rightBinExpr!=null) {
if(rightBinExpr.left isSameAs assign2.target) {
// X = Something <oper> (X <oper> Right)
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
rightBinExpr.left, zero, rightBinExpr
))
}
if(rightBinExpr.right isSameAs assign2.target) {
// X = Something <oper> (Left <oper> X)
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
rightBinExpr.right, zero, rightBinExpr
))
}
}
}
return linesToRemove
fun substituteZeroInPrefixexpr(expr: PrefixExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
if(expr.expression isSameAs assign2.target) {
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
expr.expression, zero, expr
))
}
}
fun substituteZeroInTypecast(expr: TypecastExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
if(expr.expression isSameAs assign2.target) {
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
expr.expression, zero, expr
))
}
val subCast = expr.expression as? TypecastExpression
if(subCast!=null && subCast.expression isSameAs assign2.target) {
linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode(
subCast.expression, zero, subCast
))
}
}
for (stmtPairs in statements.windowed(2, step = 1)) {
val assign1 = stmtPairs[0] as? Assignment
val assign2 = stmtPairs[1] as? Assignment
if (assign1 != null && assign2 != null) {
val cvalue1 = assign1.value.constValue(program)
if(cvalue1!=null && cvalue1.number==0.0 && assign2.target.isSameAs(assign1.target, program) && assign2.isAugmentable) {
val value2 = assign2.value
val zero = VarDecl.defaultZero(value2.inferType(program).getOr(DataType.UNDEFINED), value2.position)
when(value2) {
is BinaryExpression -> substituteZeroInBinexpr(value2, zero, assign1, assign2)
is PrefixExpression -> substituteZeroInPrefixexpr(value2, zero, assign1, assign2)
is TypecastExpression -> substituteZeroInTypecast(value2, zero, assign1, assign2)
else -> {}
}
} else {
if (assign1.target.isSameAs(assign2.target, program) && !assign1.target.isIOAddress(compTarget.machine)) {
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(assign2.target.identifier!!.nameInSource))
// only remove the second assignment if its value is a simple expression!
when(assign2.value) {
is PrefixExpression,
is BinaryExpression,
is TypecastExpression,
is FunctionCall -> { /* don't remove */ }
else -> linesToRemove.add(assign1)
}
}
}
}
}
return modifications + linesToRemove.map { IAstModification.Remove(it, scope) }
}
}
+3
View File
@@ -33,6 +33,9 @@ graphics {
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
; Bresenham algorithm.
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
; TODO implement this as optimized assembly, for instance https://github.com/EgonOlsen71/bresenham/blob/main/src/asm/graphics.asm ??
; or from here https://retro64.altervista.org/blog/an-introduction-to-vector-based-graphics-the-commodore-64-rotating-simple-3d-objects/
if y1>y2 {
; make sure dy is always positive to have only 4 instead of 8 special cases
swap(x1, x2)
+6 -34
View File
@@ -597,62 +597,34 @@ _longcopy
}}
}
inline asmsub rsave() {
; save cpu status flag and all registers A, X, Y.
; see http://6502.org/tutorials/register_preservation.html
%asm {{
php
sta P8ZP_SCRATCH_REG
pha
txa
pha
tya
pha
lda P8ZP_SCRATCH_REG
}}
}
inline asmsub rrestore() {
; restore all registers and cpu status flag
%asm {{
pla
tay
pla
tax
pla
plp
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
pla
php
pla
}}
}
inline asmsub clear_carry() {
%asm {{
clc
clc
}}
}
inline asmsub set_carry() {
%asm {{
sec
sec
}}
}
inline asmsub clear_irqd() {
%asm {{
cli
cli
}}
}
inline asmsub set_irqd() {
%asm {{
sei
sei
}}
}
+11 -11
View File
@@ -12,14 +12,14 @@ conv {
asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
%asm {{
phx
stx P8ZP_SCRATCH_REG
jsr conv.ubyte2decimal
sty string_out
sta string_out+1
stx string_out+2
lda #0
sta string_out+3
plx
ldx P8ZP_SCRATCH_REG
rts
}}
}
@@ -27,7 +27,7 @@ asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) {
asmsub str_ub (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in decimal string form, without left padding 0s
%asm {{
phx
stx P8ZP_SCRATCH_REG
ldy #0
sty P8ZP_SCRATCH_B1
jsr conv.ubyte2decimal
@@ -53,7 +53,7 @@ _output_byte_digits
iny
lda #0
sta string_out,y
plx
ldx P8ZP_SCRATCH_REG
rts
}}
}
@@ -61,7 +61,7 @@ _output_byte_digits
asmsub str_b (byte value @ A) clobbers(A,Y) {
; ---- convert the byte in A in decimal string form, without left padding 0s
%asm {{
phx
stx P8ZP_SCRATCH_REG
ldy #0
sty P8ZP_SCRATCH_B1
cmp #0
@@ -149,7 +149,7 @@ asmsub str_uwhex (uword value @ AY) clobbers(A,Y) {
asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
%asm {{
phx
stx P8ZP_SCRATCH_REG
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
@@ -157,7 +157,7 @@ asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
beq +
iny
bne -
+ plx
+ ldx P8ZP_SCRATCH_REG
rts
}}
}
@@ -165,7 +165,7 @@ asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
asmsub str_uw (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
%asm {{
phx
stx P8ZP_SCRATCH_REG
jsr conv.uword2decimal
ldx #0
_output_digits
@@ -183,7 +183,7 @@ _gotdigit sta string_out,x
bne _gotdigit
_end lda #0
sta string_out,x
plx
ldx P8ZP_SCRATCH_REG
rts
_allzero lda #'0'
@@ -198,7 +198,7 @@ asmsub str_w (word value @ AY) clobbers(A,Y) {
%asm {{
cpy #0
bpl str_uw
phx
stx P8ZP_SCRATCH_REG
pha
lda #'-'
sta string_out
@@ -516,7 +516,7 @@ asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
;By Omegamatrix Further optimizations by tepples
; routine from http://forums.nesdev.com/viewtopic.php?f=2&t=11341&start=15
; routine from https://forums.nesdev.org/viewtopic.php?f=2&t=11341&start=15
;HexToDec99
; start in A
+6 -7
View File
@@ -775,6 +775,7 @@ _done
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
; You must also have called text_charset() first to select and prepare the character set to use.
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to multiples of 8 ! TODO allow per-pixel horizontal positioning
; TODO draw whole horizontal spans using vera auto increment if possible, instead of per-character columns
uword chardataptr
when active_mode {
1, 5 -> {
@@ -808,11 +809,8 @@ _done
sta cx16.VERA_ADDR_L
bcc +
inc cx16.VERA_ADDR_M
+ lda x
clc
adc #1
sta x
bcc +
+ inc x
bne +
inc x+1
+ dey
bne -
@@ -826,7 +824,6 @@ _done
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1)
repeat 8 {
; TODO rewrite this inner loop fully in assembly
position(x,y)
y++
%asm {{
@@ -855,7 +852,9 @@ _done
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
repeat 8 {
; TODO rewrite this inner loop fully in assembly
; TODO rewrite this inner loop partly in assembly
; requires expanding the charbits to 2-bits per pixel (based on color)
; also it's way more efficient to draw whole horizontal spans instead of per-character
ubyte charbits = cx16.vpeek(charset_bank, chardataptr)
repeat 8 {
charbits <<= 1
+7 -28
View File
@@ -312,7 +312,7 @@ romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered
; TODO specify the correct clobbers for all functions below, we now assume all 3 regs are clobbered
; high level graphics & fonts
romsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
@@ -367,14 +367,14 @@ romsub $fecc = monitor() clobbers(A,X,Y)
; ---- utilities -----
inline asmsub rombank(ubyte rombank @A) {
inline asmsub rombank(ubyte bank @A) {
; -- set the rom banks
%asm {{
sta $01 ; rom bank register (v39+, used to be cx16.d1prb $9f60 in v38)
}}
}
inline asmsub rambank(ubyte rambank @A) {
inline asmsub rambank(ubyte bank @A) {
; -- set the ram bank
%asm {{
sta $00 ; ram bank register (v39+, used to be cx16.d1pra $9f61 in v38)
@@ -859,27 +859,6 @@ sys {
}}
}
inline asmsub rsave() {
; save cpu status flag and all registers A, X, Y.
; see http://6502.org/tutorials/register_preservation.html
%asm {{
php
pha
phy
phx
}}
}
inline asmsub rrestore() {
; restore all registers and cpu status flag
%asm {{
plx
ply
pla
plp
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
@@ -889,25 +868,25 @@ sys {
inline asmsub clear_carry() {
%asm {{
clc
clc
}}
}
inline asmsub set_carry() {
%asm {{
sec
sec
}}
}
inline asmsub clear_irqd() {
%asm {{
cli
cli
}}
}
inline asmsub set_irqd() {
%asm {{
sei
sei
}}
}
+2 -3
View File
@@ -565,9 +565,8 @@ asmsub print_w (word value @ AY) clobbers(A,Y) {
tay
pla
eor #255
clc
adc #1
bcc +
ina
bne +
iny
+ bra print_uw
}}
+1 -1
View File
@@ -1 +1 @@
7.3
7.4
+31 -9
View File
@@ -3,6 +3,7 @@ package prog8
import kotlinx.cli.*
import prog8.ast.base.AstException
import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
@@ -66,6 +67,11 @@ private fun compileMain(args: Array<String>): Boolean {
if(srcdirs.firstOrNull()!=".")
srcdirs.add(0, ".")
if (compilationTarget != C64Target.name && compilationTarget != Cx16Target.name) {
System.err.println("Invalid compilation target: $compilationTarget")
return false
}
if(watchMode==true) {
val watchservice = FileSystems.getDefault().newWatchService()
val allImportedFiles = mutableSetOf<Path>()
@@ -75,10 +81,18 @@ private fun compileMain(args: Array<String>): Boolean {
val results = mutableListOf<CompilationResult>()
for(filepathRaw in moduleFiles) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult = compileProgram(filepath,
dontOptimize!=true, optimizeFloatExpressions==true,
dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true,
compilationTarget, srcdirs, outputPath)
val args = CompilerArguments(
filepath,
dontOptimize != true,
optimizeFloatExpressions == true,
dontWriteAssembly != true,
slowCodegenWarnings == true,
quietAssembler == true,
compilationTarget,
srcdirs,
outputPath
)
val compilationResult = compileProgram(args)
results.add(compilationResult)
}
@@ -115,11 +129,19 @@ private fun compileMain(args: Array<String>): Boolean {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath,
dontOptimize!=true, optimizeFloatExpressions==true,
dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true,
compilationTarget, srcdirs, outputPath)
if(!compilationResult.success)
val args = CompilerArguments(
filepath,
dontOptimize != true,
optimizeFloatExpressions == true,
dontWriteAssembly != true,
slowCodegenWarnings == true,
quietAssembler == true,
compilationTarget,
srcdirs,
outputPath
)
compilationResult = compileProgram(args)
if (!compilationResult.success)
return false
} catch (x: AstException) {
return false
@@ -13,16 +13,28 @@ import prog8.ast.walk.IAstVisitor
import prog8.compiler.astprocessing.isSubroutineParameter
import prog8.compiler.target.AssemblyError
import prog8.compilerinterface.*
import prog8.optimizer.getTempVarName
internal class BeforeAsmGenerationAstChanger(val program: Program, private val options: CompilationOptions,
private val errors: IErrorReporter) : AstWalker() {
private val subroutineVariables = mutableMapOf<Subroutine, MutableList<Pair<String, VarDecl>>>()
private fun rememberSubroutineVar(decl: VarDecl) {
val sub = decl.definingSubroutine ?: return
var varsList = subroutineVariables[sub]
if(varsList==null) {
varsList = mutableListOf()
subroutineVariables[sub] = varsList
}
varsList.add(decl.name to decl)
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.type==VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
throw FatalAstException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
subroutineVariables.add(decl.name to decl)
rememberSubroutineVar(decl)
return noModifications
}
@@ -32,7 +44,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
if(!assignment.isAugmentable
&& assignment.target.identifier != null
&& assignment.target.isInRegularRAMof(options.compTarget.machine)) {
&& !assignment.target.isIOAddress(options.compTarget.machine)) {
val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null && binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
@@ -40,7 +52,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
if (binExpr != null && binExpr.operator !in comparisonOperators) {
if (binExpr.left !is BinaryExpression) {
if (binExpr.right.referencesIdentifier(*assignment.target.identifier!!.nameInSource.toTypedArray())) {
if (binExpr.right.referencesIdentifier(assignment.target.identifier!!.nameInSource)) {
// the right part of the expression contains the target variable itself.
// we can't 'split' it trivially because the variable will be changed halfway through.
if(binExpr.operator in associativeOperators) {
@@ -68,47 +80,16 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
return noModifications
}
private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>()
private val addedIfConditionVars = mutableSetOf<Pair<Subroutine, String>>()
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
subroutineVariables.clear()
addedIfConditionVars.clear()
if(!subroutine.isAsmSubroutine) {
// change 'str' parameters into 'uword' (just treat it as an address)
val stringParams = subroutine.parameters.filter { it.type==DataType.STR }
val parameterChanges = stringParams.map {
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.position)
IAstModification.ReplaceNode(it, uwordParam, subroutine)
}
val stringParamNames = stringParams.map { it.name }.toSet()
val varsChanges = subroutine.statements
.filterIsInstance<VarDecl>()
.filter { it.autogeneratedDontRemove && it.name in stringParamNames }
.map {
val newvar = VarDecl(it.type, DataType.UWORD, it.zeropage, null, it.name, null, false, true, it.sharedWithAsm, it.position)
IAstModification.ReplaceNode(it, newvar, subroutine)
}
return parameterChanges + varsChanges
}
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
if(scope.statements.any { it is VarDecl || it is IStatementContainer })
throw FatalAstException("anonymousscope may no longer contain any vardecls or subscopes")
val decls = scope.statements.filterIsInstance<VarDecl>().filter { it.type == VarDeclType.VAR }
subroutineVariables.addAll(decls.map { it.name to it })
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val firstDeclarations = mutableMapOf<String, VarDecl>()
for(decl in subroutineVariables) {
val rememberedSubroutineVars = subroutineVariables.getOrDefault(subroutine, mutableListOf())
for(decl in rememberedSubroutineVars) {
val existing = firstDeclarations[decl.first]
if(existing!=null && existing !== decl.second) {
errors.err("variable ${decl.first} already defined in subroutine ${subroutine.name} at ${existing.position}", decl.second.position)
@@ -116,7 +97,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
firstDeclarations[decl.first] = decl.second
}
}
rememberedSubroutineVars.clear()
// 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 some other situations.
@@ -206,8 +187,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
}
if((binExpr.left as? NumericLiteralValue)?.number==0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0)
if((binExpr.left as? NumericLiteralValue)?.number==0.0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0.0)
throw FatalAstException("0==X should have been swapped to if X==0")
// simplify the conditional expression, introduce simple assignments if required.
@@ -248,15 +229,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
val separateRightExpr = !expr.right.isSimple && expr.right !is IFunctionCall
if(separateLeftExpr) {
val dt = expr.left.inferType(program)
val name = when {
// TODO assume (hope) cx16.r9 isn't used for anything else...
dt.istype(DataType.UBYTE) -> listOf("cx16","r9L")
dt.istype(DataType.BYTE) -> listOf("cx16","r9sL")
dt.istype(DataType.UWORD) -> listOf("cx16","r9")
dt.istype(DataType.WORD) -> listOf("cx16","r9s")
else -> throw AssemblyError("invalid dt")
}
val name = getTempVarName(expr.left.inferType(program))
leftOperandReplacement = IdentifierReference(name, expr.position)
leftAssignment = Assignment(
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
@@ -302,8 +275,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
}
if((binExpr.left as? NumericLiteralValue)?.number==0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0)
if((binExpr.left as? NumericLiteralValue)?.number==0.0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0.0)
throw FatalAstException("0==X should have been swapped to if X==0")
// simplify the conditional expression, introduce simple assignments if required.
@@ -339,8 +312,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
}
if((binExpr.left as? NumericLiteralValue)?.number==0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0)
if((binExpr.left as? NumericLiteralValue)?.number==0.0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0.0)
throw FatalAstException("0==X should have been swapped to if X==0")
// TODO simplify the conditional expression, introduce simple assignments if required.
+28 -26
View File
@@ -28,26 +28,27 @@ class CompilationResult(val success: Boolean,
val compTarget: ICompilationTarget,
val importedFiles: List<Path>)
class CompilerArguments(val filepath: Path,
val optimize: Boolean,
val optimizeFloatExpressions: Boolean,
val writeAssembly: Boolean,
val slowCodegenWarnings: Boolean,
val quietAssembler: Boolean,
val compilationTarget: String,
val sourceDirs: List<String> = emptyList(),
val outputDir: Path = Path(""),
val errors: IErrorReporter = ErrorReporter())
// TODO refactor the gigantic list of parameters
fun compileProgram(filepath: Path,
optimize: Boolean,
optimizeFloatExpressions: Boolean,
writeAssembly: Boolean,
slowCodegenWarnings: Boolean,
quietAssembler: Boolean,
compilationTarget: String,
sourceDirs: List<String>,
outputDir: Path,
errors: IErrorReporter = ErrorReporter()): CompilationResult {
fun compileProgram(args: CompilerArguments): CompilationResult {
var programName = ""
lateinit var program: Program
lateinit var importedFiles: List<Path>
val optimizeFloatExpr = if(optimize) optimizeFloatExpressions else false
val optimizeFloatExpr = if(args.optimize) args.optimizeFloatExpressions else false
val compTarget =
when(compilationTarget) {
when(args.compilationTarget) {
C64Target.name -> C64Target
Cx16Target.name -> Cx16Target
else -> throw IllegalArgumentException("invalid compilation target")
@@ -56,30 +57,30 @@ fun compileProgram(filepath: Path,
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (programresult, compilationOptions, imported) = parseImports(filepath, errors, compTarget, sourceDirs)
val (programresult, compilationOptions, imported) = parseImports(args.filepath, args.errors, compTarget, args.sourceDirs)
with(compilationOptions) {
this.slowCodegenWarnings = slowCodegenWarnings
this.optimize = optimize
this.slowCodegenWarnings = args.slowCodegenWarnings
this.optimize = args.optimize
this.optimizeFloatExpressions = optimizeFloatExpr
}
program = programresult
importedFiles = imported
processAst(program, errors, compilationOptions)
processAst(program, args.errors, compilationOptions)
if (compilationOptions.optimize)
optimizeAst(
program,
compilationOptions,
errors,
args.errors,
BuiltinFunctionsFacade(BuiltinFunctions),
compTarget
)
postprocessAst(program, errors, compilationOptions)
postprocessAst(program, args.errors, compilationOptions)
// println("*********** AST BEFORE ASSEMBLYGEN *************")
// printAst(program)
// printProgram(program)
if (writeAssembly) {
val result = writeAssembly(program, errors, outputDir, quietAssembler, compilationOptions)
if (args.writeAssembly) {
val result = writeAssembly(program, args.errors, args.outputDir, args.quietAssembler, compilationOptions)
when (result) {
is WriteAssemblyResult.Ok -> programName = result.filename
is WriteAssemblyResult.Fail -> {
@@ -255,16 +256,17 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing for target ${compilerOptions.compTarget.name}...")
program.preprocessAst()
program.preprocessAst(program)
program.checkIdentifiers(errors, compilerOptions)
errors.report()
// TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR
// ...but what do we gain from this? We can leave it as it is now: where a char literal is no more than syntactic sugar for an UBYTE value.
// By introduction a CHAR dt, we will also lose the opportunity to do constant-folding on any expression containing a char literal.
// Yes this is different from strings that are only encoded in the code gen phase.
program.charLiteralsToUByteLiterals(compilerOptions.compTarget)
program.constantFold(errors, compilerOptions.compTarget)
errors.report()
program.reorderStatements(errors)
program.reorderStatements(errors, compilerOptions)
errors.report()
program.addTypecasts(errors)
errors.report()
@@ -327,7 +329,7 @@ private fun writeAssembly(program: Program,
errors.report()
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
// printAst(program)
// printProgram(program)
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
val assembly = asmGeneratorFor(compilerOptions.compTarget,
@@ -350,7 +352,7 @@ private fun writeAssembly(program: Program,
}
}
fun printAst(program: Program) {
fun printProgram(program: Program) {
println()
val printer = AstToSourceTextConverter(::print, program)
printer.visit(program)
@@ -51,7 +51,7 @@ class ModuleImporter(private val program: Program,
fun importLibraryModule(name: String): Module? {
val import = Directive("%import", listOf(
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
DirectiveArg("", name, 42u, position = Position("<<<implicit-import>>>", 0, 0, 0))
), Position("<<<implicit-import>>>", 0, 0, 0))
return executeImportDirective(import, null)
}
@@ -92,9 +92,9 @@ internal class AstChecker(private val program: Program,
fun checkUnsignedLoopDownto0(range: RangeExpr?) {
if(range==null)
return
val step = range.step.constValue(program)?.number?.toDouble() ?: 1.0
val step = range.step.constValue(program)?.number ?: 1.0
if(step < -1.0) {
val limit = range.to.constValue(program)?.number?.toDouble()
val limit = range.to.constValue(program)?.number
if(limit==0.0 && range.from.constValue(program)==null)
errors.err("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping", forLoop.position)
}
@@ -169,17 +169,20 @@ internal class AstChecker(private val program: Program,
if(targetStatement is BuiltinFunctionStatementPlaceholder)
errors.err("can't jump to a builtin function", jump.position)
}
if(!jump.isGosub && targetStatement is Subroutine && targetStatement.parameters.any()) {
errors.err("can't jump to a subroutine that takes parameters", jump.position)
}
}
val addr = jump.address
if(addr!=null && (addr < 0 || addr > 65535))
if(addr!=null && addr > 65535u)
errors.err("jump address must be valid integer 0..\$ffff", jump.position)
super.visit(jump)
}
override fun visit(block: Block) {
val addr = block.address
if(addr!=null && (addr<0 || addr>65535)) {
if(addr!=null && addr>65535u) {
errors.err("block memory address must be valid integer 0..\$ffff", block.position)
}
@@ -224,7 +227,8 @@ internal class AstChecker(private val program: Program,
count++
}
override fun visit(jump: Jump) {
count++
if(!jump.isGosub)
count++
}
}
@@ -414,11 +418,10 @@ internal class AstChecker(private val program: Program,
val stmt = (assignment.value as FunctionCall).target.targetStatement(program)
if (stmt is Subroutine) {
val idt = assignment.target.inferType(program)
if(!idt.isKnown) {
errors.err("return type mismatch", assignment.value.position)
}
if(!idt.isKnown)
throw FatalAstException("assignment target invalid dt")
if(stmt.returntypes.isEmpty() || (stmt.returntypes.size == 1 && stmt.returntypes.single() isNotAssignableTo idt.getOr(DataType.BYTE))) {
errors.err("return type mismatch", assignment.value.position)
errors.err("return type mismatch: ${stmt.returntypes.single()} expected $idt", assignment.value.position)
}
}
}
@@ -428,8 +431,15 @@ internal class AstChecker(private val program: Program,
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
if(targetDt.isIterable)
errors.err("cannot assign value to string or array", assignment.value.position)
else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD))
errors.err("type of value $valueDt doesn't match target $targetDt", assignment.value.position)
else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD)) {
if(targetDt.isUnknown) {
if(assignment.target.identifier?.targetStatement(program)!=null)
errors.err("target datatype is unknown", assignment.target.position)
// otherwise, another error about missing symbol is already reported.
} else {
errors.err("type of value $valueDt doesn't match target $targetDt", assignment.value.position)
}
}
}
if(assignment.value is TypecastExpression) {
@@ -489,7 +499,7 @@ internal class AstChecker(private val program: Program,
if (assignment.value !is FunctionCall)
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
} else {
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE), assignTarget,
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE),
sourceDatatype.getOr(DataType.BYTE), assignment.value, assignment.position)
}
}
@@ -508,7 +518,7 @@ internal class AstChecker(private val program: Program,
fun err(msg: String) = errors.err(msg, decl.position)
// the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true)
if(decl.value?.referencesIdentifier(listOf(decl.name)) == true || decl.arraysize?.indexExpr?.referencesIdentifier(listOf(decl.name)) == true)
err("recursive var declaration")
// CONST can only occur on simple types (byte, word, float)
@@ -826,7 +836,7 @@ internal class AstChecker(private val program: Program,
when(expr.operator){
"/", "%" -> {
val constvalRight = expr.right.constValue(program)
val divisor = constvalRight?.number?.toDouble()
val divisor = constvalRight?.number
if(divisor==0.0)
errors.err("division by zero", expr.right.position)
if(expr.operator=="%") {
@@ -966,36 +976,43 @@ internal class AstChecker(private val program: Program,
override fun visit(functionCallStatement: FunctionCallStatement) {
val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement)
if(targetStatement!=null)
if(targetStatement!=null) {
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
if (!functionCallStatement.void) {
// check for unused return values
if (targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty()) {
if(targetStatement.returntypes.size==1)
errors.warn("result value of subroutine call is discarded (use void?)", functionCallStatement.position)
else
errors.warn("result values of subroutine call are discarded (use void?)", functionCallStatement.position)
}
else if(targetStatement is BuiltinFunctionStatementPlaceholder) {
val rt = builtinFunctionReturnType(targetStatement.name, functionCallStatement.args, program)
if(rt.isKnown)
errors.warn("result value of a function call is discarded (use void?)", functionCallStatement.position)
}
checkUnusedReturnValues(functionCallStatement, targetStatement, program, errors)
}
if(functionCallStatement.target.nameInSource.last() == "sort") {
// sort is not supported on float arrays
val idref = functionCallStatement.args.singleOrNull() as? IdentifierReference
if(idref!=null && idref.inferType(program) istype DataType.ARRAY_F) {
errors.err("sorting a floating point array is not supported", functionCallStatement.args.first().position)
}
}
val funcName = functionCallStatement.target.nameInSource
if(functionCallStatement.target.nameInSource.last() in arrayOf("rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
// in-place modification, can't be done on literals
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
if(funcName.size==1) {
// check some builtin function calls
if(funcName[0] == "sort") {
// sort is not supported on float arrays
val idref = functionCallStatement.args.singleOrNull() as? IdentifierReference
if(idref!=null && idref.inferType(program) istype DataType.ARRAY_F) {
errors.err("sorting a floating point array is not supported", functionCallStatement.args.first().position)
}
}
else if(funcName[0] in arrayOf("pop", "popw")) {
// can only pop into a variable, that has to have the correct type
val idref = functionCallStatement.args[0]
if(idref !is IdentifierReference) {
if(idref is TypecastExpression) {
val passByRef = idref.expression.inferType(program).isPassByReference
if(idref.type!=DataType.UWORD || !passByRef)
errors.err("invalid argument to pop, must be a variable with the correct type: ${functionCallStatement.args.first()}", functionCallStatement.args.first().position)
} else {
errors.err("invalid argument to pop, must be a variable with the correct type: ${functionCallStatement.args.first()}", functionCallStatement.args.first().position)
}
}
}
if(funcName[0] in arrayOf("rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
// in-place modification, can't be done on literals
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
}
}
}
val error =
@@ -1053,7 +1070,12 @@ internal class AstChecker(private val program: Program,
ident = fcall.args[0] as? IdentifierReference
}
if(ident!=null && ident.nameInSource[0] == "cx16" && ident.nameInSource[1].startsWith("r")) {
val reg = RegisterOrPair.valueOf(ident.nameInSource[1].uppercase())
var regname = ident.nameInSource[1].uppercase()
if(regname.endsWith('L'))
regname=regname.substring(0, regname.length-1)
if(regname.endsWith('s'))
regname=regname.substring(0, regname.length-1)
val reg = RegisterOrPair.valueOf(regname)
val same = params.filter { it.value.registerOrPair==reg }
for(s in same) {
if(s.index!=arg.index) {
@@ -1273,7 +1295,7 @@ internal class AstChecker(private val program: Program,
}
when (targetDt) {
DataType.FLOAT -> {
val number=value.number.toDouble()
val number=value.number
if (number > 1.7014118345e+38 || number < -1.7014118345e+38)
return err("value '$number' out of range for MFLPT format")
}
@@ -1349,7 +1371,6 @@ internal class AstChecker(private val program: Program,
}
private fun checkAssignmentCompatible(targetDatatype: DataType,
target: AssignTarget,
sourceDatatype: DataType,
sourceValue: Expression,
position: Position) : Boolean {
@@ -1392,3 +1413,19 @@ internal class AstChecker(private val program: Program,
return false
}
}
internal fun checkUnusedReturnValues(call: FunctionCallStatement, target: Statement, program: Program, errors: IErrorReporter) {
if (!call.void) {
// check for unused return values
if (target is Subroutine && target.returntypes.isNotEmpty()) {
if (target.returntypes.size == 1)
errors.warn("result value of subroutine call is discarded (use void?)", call.position)
else
errors.warn("result values of subroutine call are discarded (use void?)", call.position)
} else if (target is BuiltinFunctionStatementPlaceholder) {
val rt = builtinFunctionReturnType(target.name, call.args, program)
if (rt.isKnown)
errors.warn("result value of a function call is discarded (use void?)", call.position)
}
}
}
@@ -11,6 +11,7 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.IStringEncoding
@@ -28,8 +29,8 @@ internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationO
}
}
internal fun Program.reorderStatements(errors: IErrorReporter) {
val reorder = StatementReorderer(this, errors)
internal fun Program.reorderStatements(errors: IErrorReporter, options: CompilationOptions) {
val reorder = StatementReorderer(this, errors, options)
reorder.visit(this)
if(errors.noErrors()) {
reorder.applyModifications()
@@ -44,7 +45,7 @@ internal fun Program.charLiteralsToUByteLiterals(enc: IStringEncoding) {
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.ReplaceNode(
char,
NumericLiteralValue(DataType.UBYTE, enc.encodeString(char.value.toString(), char.altEncoding)[0].toInt(), char.position),
NumericLiteralValue(DataType.UBYTE, enc.encodeString(char.value.toString(), char.altEncoding)[0].toDouble(), char.position),
parent
))
}
@@ -64,8 +65,8 @@ internal fun Program.verifyFunctionArgTypes() {
fixer.visit(this)
}
internal fun Program.preprocessAst() {
val transforms = AstPreprocessor()
internal fun Program.preprocessAst(program: Program) {
val transforms = AstPreprocessor(program)
transforms.visit(this)
var mods = transforms.applyModifications()
while(mods>0)
@@ -75,7 +75,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter, private
val paramsToCheck = paramNames.intersect(namesInSub)
for(name in paramsToCheck) {
val symbol = subroutine.searchSymbol(name)
if(symbol!=null && symbol.position != subroutine.position)
if(symbol!=null && (symbol as? VarDecl)?.subroutineParameter==null)
nameError(name, symbol.position, subroutine)
}
@@ -1,9 +1,13 @@
package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.SyntaxError
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
@@ -12,7 +16,40 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
class AstPreprocessor : AstWalker() {
class AstPreprocessor(val program: Program) : AstWalker() {
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> {
// has to be done before the constant folding, otherwise certain checks there will fail on invalid range sizes
val modifications = mutableListOf<IAstModification>()
if(range.from !is NumericLiteralValue) {
try {
val constval = range.from.constValue(program)
if (constval != null)
modifications += IAstModification.ReplaceNode(range.from, constval, range)
} catch (x: SyntaxError) {
// syntax errors will be reported later
}
}
if(range.to !is NumericLiteralValue) {
try {
val constval = range.to.constValue(program)
if(constval!=null)
modifications += IAstModification.ReplaceNode(range.to, constval, range)
} catch (x: SyntaxError) {
// syntax errors will be reported later
}
}
if(range.step !is NumericLiteralValue) {
try {
val constval = range.step.constValue(program)
if(constval!=null)
modifications += IAstModification.ReplaceNode(range.step, constval, range)
} catch (x: SyntaxError) {
// syntax errors will be reported later
}
}
return modifications
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
@@ -20,13 +20,13 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
val symbolsInSub = subroutine.allDefinedSymbols
val namesInSub = symbolsInSub.map{ it.first }.toSet()
if(subroutine.asmAddress==null) {
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
if(!subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty()) {
val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet()
if(!vars.containsAll(subroutine.parameters.map{it.name})) {
return subroutine.parameters
.filter { it.name !in namesInSub }
.map {
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
val vardecl = VarDecl.fromParameter(it)
IAstModification.InsertFirst(vardecl, subroutine)
}
}
@@ -7,10 +7,14 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
internal class StatementReorderer(val program: Program, val errors: IErrorReporter) : AstWalker() {
internal class StatementReorderer(val program: Program,
val errors: IErrorReporter,
private val options: CompilationOptions) : AstWalker() {
// Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set.
// - library blocks are put last.
@@ -21,12 +25,13 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
// - sorts the choices in when statement.
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc.).
// - replace subroutine calls (statement) by just assigning the arguments to the parameters and then a GoSub to the routine.
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
val (blocks, other) = module.statements.partition { it is Block }
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: UInt.MAX_VALUE }).toMutableList()
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
if(mainBlock!=null && mainBlock.address==null) {
@@ -56,11 +61,9 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
return noModifications
}
val nextStmt = decl.nextSibling()
val nextAssign = nextStmt as? Assignment
val nextFor = nextStmt as? ForLoop
val hasNextForWithThisLoopvar = nextFor?.loopVar?.nameInSource==listOf(decl.name)
val hasNextAssignment = nextAssign!=null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY)
if (!hasNextAssignment && !hasNextForWithThisLoopvar) {
if (!hasNextForWithThisLoopvar) {
// Add assignment to initialize with zero
// Note: for block-level vars, this will introduce assignments in the block scope. These have to be dealt with correctly later.
val identifier = IdentifierReference(listOf(decl.name), decl.position)
@@ -124,6 +127,30 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
subs.map { IAstModification.InsertLast(it, subroutine) }
}
if(!subroutine.isAsmSubroutine) {
// change 'str' parameters into 'uword' (just treat it as an address)
val stringParams = subroutine.parameters.filter { it.type==DataType.STR }
val parameterChanges = stringParams.map {
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.position)
IAstModification.ReplaceNode(it, uwordParam, subroutine)
}
val stringParamsByNames = stringParams.associateBy { it.name }
val varsChanges =
if(stringParamsByNames.isNotEmpty()) {
subroutine.statements
.filterIsInstance<VarDecl>()
.filter { it.subroutineParameter!=null && it.name in stringParamsByNames }
.map {
val newvar = VarDecl(it.type, DataType.UWORD, it.zeropage, null, it.name, null, false, true, it.sharedWithAsm, stringParamsByNames.getValue(it.name), it.position)
IAstModification.ReplaceNode(it, newvar, subroutine)
}
}
else emptyList()
return parameterChanges + varsChanges
}
return noModifications
}
@@ -197,7 +224,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
// generating the wrong results later
fun wrapped(expr: Expression): Expression =
BinaryExpression(expr, "!=", NumericLiteralValue(DataType.UBYTE, 0, expr.position), expr.position)
BinaryExpression(expr, "!=", NumericLiteralValue(DataType.UBYTE, 0.0, expr.position), expr.position)
fun isLogicalExpr(expr: Expression?): Boolean {
if(expr is BinaryExpression && expr.operator in (logicalOperators + comparisonOperators))
@@ -335,4 +362,110 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
)
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
val function = functionCallStatement.target.targetStatement(program)!!
checkUnusedReturnValues(functionCallStatement, function, program, errors)
if(function is Subroutine) {
if(function.inline)
return noModifications
return if(function.isAsmSubroutine)
replaceCallAsmSubStatementWithGosub(function, functionCallStatement, parent)
else
replaceCallSubStatementWithGosub(function, functionCallStatement, parent)
}
return noModifications
}
private fun replaceCallSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(function.parameters.isEmpty()) {
// 0 params -> just GoSub
return listOf(IAstModification.ReplaceNode(call, GoSub(null, call.target, null, call.position), parent))
}
val assignParams =
function.parameters.zip(call.args).map {
var argumentValue = it.second
val paramIdentifier = IdentifierReference(function.scopedName + it.first.name, argumentValue.position)
val argDt = argumentValue.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
if(argDt in ArrayDatatypes) {
// pass the address of the array instead
argumentValue = AddressOf(argumentValue as IdentifierReference, argumentValue.position)
}
Assignment(AssignTarget(paramIdentifier, null, null, argumentValue.position), argumentValue, argumentValue.position)
}
val scope = AnonymousScope(assignParams.toMutableList(), call.position)
scope.statements += GoSub(null, call.target, null, call.position)
return listOf(IAstModification.ReplaceNode(call, scope, parent))
}
private fun replaceCallAsmSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(function.parameters.isEmpty()) {
// 0 params -> just GoSub
val scope = AnonymousScope(mutableListOf(), call.position)
if(function.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
}
scope.statements += GoSub(null, call.target, null, call.position)
if(function.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
}
return listOf(IAstModification.ReplaceNode(call, scope, parent))
} else if(!options.compTarget.asmsubArgsHaveRegisterClobberRisk(call.args, function.asmParameterRegisters)) {
// No register clobber risk, let the asmgen assign values to the registers directly.
// this is more efficient than first evaluating them to the stack.
// As complex expressions will be flagged as a clobber-risk, these will be simplified below.
return noModifications
} else {
// clobber risk; evaluate the arguments on the CPU stack first (in reverse order)...
if (options.slowCodegenWarnings)
errors.warn("slow argument passing used to avoid register clobbering", call.position)
val argOrder = options.compTarget.asmsubArgsEvalOrder(function)
val scope = AnonymousScope(mutableListOf(), call.position)
if(function.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
}
argOrder.reversed().forEach {
val arg = call.args[it]
val param = function.parameters[it]
scope.statements += pushCall(arg, param.type, arg.position)
}
// ... and pop them off again into the registers.
argOrder.forEach {
val param = function.parameters[it]
val targetName = function.scopedName + param.name
scope.statements += popCall(targetName, param.type, call.position)
}
scope.statements += GoSub(null, call.target, null, call.position)
if(function.shouldSaveX()) {
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
}
return listOf(IAstModification.ReplaceNode(call, scope, parent))
}
}
private fun popCall(targetName: List<String>, dt: DataType, position: Position): FunctionCallStatement {
return FunctionCallStatement(
IdentifierReference(listOf(if(dt in ByteDatatypes) "pop" else "popw"), position),
mutableListOf(IdentifierReference(targetName, position)),
true, position
)
}
private fun pushCall(value: Expression, dt: DataType, position: Position): FunctionCallStatement {
val pushvalue = when(dt) {
DataType.UBYTE, DataType.UWORD -> value
in PassByReferenceDatatypes -> value
DataType.BYTE -> TypecastExpression(value, DataType.UBYTE, true, position)
DataType.WORD -> TypecastExpression(value, DataType.UWORD, true, position)
else -> throw FatalAstException("invalid dt $dt $value")
}
return FunctionCallStatement(
IdentifierReference(listOf(if(dt in ByteDatatypes) "push" else "pushw"), position),
mutableListOf(pushvalue),
true, position
)
}
}
@@ -46,6 +46,27 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
// convert a negative operand for bitwise operator to the 2's complement positive number instead
if(expr.operator in bitwiseOperators && leftDt.isInteger && rightDt.isInteger) {
val leftCv = expr.left.constValue(program)
if(leftCv!=null && leftCv.number<0) {
val value = if(rightDt.isBytes) 256+leftCv.number else 65536+leftCv.number
return listOf(IAstModification.ReplaceNode(
expr.left,
NumericLiteralValue(rightDt.getOr(DataType.UNDEFINED), value, expr.left.position),
expr))
}
val rightCv = expr.right.constValue(program)
if(rightCv!=null && rightCv.number<0) {
val value = if(leftDt.isBytes) 256+rightCv.number else 65536+rightCv.number
return listOf(IAstModification.ReplaceNode(
expr.right,
NumericLiteralValue(leftDt.getOr(DataType.UNDEFINED), value, expr.right.position),
expr))
}
}
// determine common datatype and add typecast as required to make left and right equal types
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.operator, expr.right)
if(toFix!=null) {
@@ -171,6 +192,18 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
call as Node)
break
}
else if(DataType.UWORD in pair.first.possibleDatatypes && argtype in PassByReferenceDatatypes) {
// We allow STR/ARRAY values in place of UWORD parameters.
// Take their address instead, UNLESS it's a str parameter in the containing subroutine
val identifier = pair.second as? IdentifierReference
if(identifier?.isSubroutineParameter(program)==false) {
modifications += IAstModification.ReplaceNode(
call.args[index],
AddressOf(identifier, pair.second.position),
call as Node)
break
}
}
}
}
}
@@ -94,6 +94,13 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.parent!==parent)
throw FatalAstException("parent node mismatch at $assignment")
val nextAssign = assignment.nextSibling() as? Assignment
if(nextAssign!=null && nextAssign.target.isSameAs(assignment.target, program)) {
if(nextAssign.value isSameAs assignment.value)
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
}
return noModifications
}
+4 -4
View File
@@ -43,7 +43,7 @@ class TestCompilerOnCharLit: FunSpec({
}
val arg = funCall.args[0] as NumericLiteralValue
arg.type shouldBe DataType.UBYTE
arg.number.toShort() shouldBe platform.encodeString("\n", false)[0]
arg.number shouldBe platform.encodeString("\n", false)[0].toDouble()
}
test("testCharVarAsRomsubArg") {
@@ -83,7 +83,7 @@ class TestCompilerOnCharLit: FunSpec({
}
val initializerValue = assignInitialValue.value as NumericLiteralValue
initializerValue.type shouldBe DataType.UBYTE
initializerValue.number.toShort() shouldBe platform.encodeString("\n", false)[0]
initializerValue.number shouldBe platform.encodeString("\n", false)[0].toDouble()
}
test("testCharConstAsRomsubArg") {
@@ -108,10 +108,10 @@ class TestCompilerOnCharLit: FunSpec({
val decl = arg.targetVarDecl(program)!!
decl.type shouldBe VarDeclType.CONST
decl.datatype shouldBe DataType.UBYTE
(decl.value as NumericLiteralValue).number.toShort() shouldBe platform.encodeString("\n", false)[0]
(decl.value as NumericLiteralValue).number shouldBe platform.encodeString("\n", false)[0]
}
is NumericLiteralValue -> {
arg.number.toShort() shouldBe platform.encodeString("\n", false)[0]
arg.number shouldBe platform.encodeString("\n", false)[0].toDouble()
}
else -> fail("invalid arg type") // funCall.args[0] shouldBe instanceOf<IdentifierReference>() // make test fail
}
+15 -11
View File
@@ -1,6 +1,8 @@
package prog8tests
import io.kotest.core.spec.style.FunSpec
import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
@@ -20,17 +22,19 @@ import kotlin.io.path.exists
private val examplesDir = assumeDirectory(workingDir, "../examples")
private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilationTarget) = compileProgram(
filepath,
optimize,
optimizeFloatExpressions = true,
writeAssembly = true,
slowCodegenWarnings = false,
quietAssembler = true,
compilationTarget = target.name,
sourceDirs = listOf(),
outputDir
)
private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilationTarget): CompilationResult {
val args = CompilerArguments(
filepath,
optimize,
optimizeFloatExpressions = true,
writeAssembly = true,
slowCodegenWarnings = false,
quietAssembler = true,
compilationTarget = target.name,
outputDir = outputDir
)
return compileProgram(args)
}
private fun prepareTestFiles(source: String, optimize: Boolean, target: ICompilationTarget): Pair<String, Path> {
val searchIn = mutableListOf(examplesDir)
+40
View File
@@ -4,11 +4,13 @@ import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.instanceOf
import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.*
import prog8.ast.statements.ForLoop
import prog8.ast.statements.VarDecl
import prog8.compiler.printProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compilerinterface.size
@@ -262,4 +264,42 @@ class TestCompilerOnRanges: FunSpec({
expr.size() shouldBe 6
expr.toConstantIntegerRange()
}
test("range with negative step should be constvalue") {
val result = compileText(C64Target, false, """
main {
sub start() {
ubyte[] array = 100 to 50 step -2
ubyte xx
for xx in 100 to 50 step -2 {
}
}
}
""").assertSuccess()
val statements = result.program.entrypoint.statements
val array = (statements[0] as VarDecl).value
array shouldBe instanceOf<ArrayLiteralValue>()
(array as ArrayLiteralValue).value.size shouldBe 26
val forloop = (statements.dropLast(1).last() as ForLoop)
forloop.iterable shouldBe instanceOf<RangeExpr>()
(forloop.iterable as RangeExpr).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY)
}
test("range with start/end variables should be ok") {
val result = compileText(C64Target, false, """
main {
sub start() {
byte from = 100
byte end = 50
byte xx
for xx in from to end step -2 {
}
}
}
""").assertSuccess()
val statements = result.program.entrypoint.statements
val forloop = (statements.dropLast(1).last() as ForLoop)
forloop.iterable shouldBe instanceOf<RangeExpr>()
(forloop.iterable as RangeExpr).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY)
}
})
+6 -2
View File
@@ -1,6 +1,8 @@
package prog8tests
import io.kotest.core.spec.style.FunSpec
import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram
import prog8.compiler.target.Cx16Target
import prog8tests.ast.helpers.assumeReadableFile
@@ -37,8 +39,8 @@ class TestCompilerOptionSourcedirs: FunSpec({
tempFileInWorkingDir.deleteExisting()
}
fun compileFile(filePath: Path, sourceDirs: List<String>) =
compileProgram(
fun compileFile(filePath: Path, sourceDirs: List<String>): CompilationResult {
val args = CompilerArguments(
filepath = filePath,
optimize = false,
optimizeFloatExpressions = false,
@@ -49,6 +51,8 @@ class TestCompilerOptionSourcedirs: FunSpec({
sourceDirs,
outputDir
)
return compileProgram(args)
}
test("testAbsoluteFilePathInWorkingDir") {
val filepath = assumeReadableFile(tempFileInWorkingDir.absolute())
+124 -77
View File
@@ -12,8 +12,9 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.PrefixExpression
import prog8.ast.statements.*
import prog8.compiler.printProgram
import prog8.compiler.target.C64Target
import prog8.compilerinterface.isInRegularRAMof
import prog8.compilerinterface.isIOAddress
import prog8.parser.SourceCode
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
@@ -22,155 +23,201 @@ import prog8tests.helpers.DummyStringEncoder
class TestMemory: FunSpec({
test("testInValidRamC64_memory_addresses") {
fun wrapWithProgram(statements: List<Statement>): Program {
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), statements.toMutableList(), false, Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
program.addModule(module)
return program
}
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
test("assignment target not in mapped IO space C64") {
var memexpr = NumericLiteralValue.optimalInteger(0x0002, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
target.isInRegularRAMof(C64Target.machine) shouldBe true
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
target.isInRegularRAMof(C64Target.machine) shouldBe true
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
target.isInRegularRAMof(C64Target.machine) shouldBe true
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
target.isInRegularRAMof(C64Target.machine) shouldBe true
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
target.isInRegularRAMof(C64Target.machine) shouldBe true
}
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
test("testNotInValidRamC64_memory_addresses") {
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
target.isInRegularRAMof(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
memexpr = NumericLiteralValue.optimalInteger(0xeeee, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
target.isInRegularRAMof(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
target.isInRegularRAMof(C64Target.machine) shouldBe false
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
target.isInRegularRAMof(C64Target.machine) shouldBe false
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
}
fun createTestProgramForMemoryRefViaVar(program: Program, address: Int, vartype: VarDeclType): AssignTarget {
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
test("assign target in mapped IO space C64") {
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe true
memexpr = NumericLiteralValue.optimalInteger(0x0001, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe true
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe true
memexpr = NumericLiteralValue.optimalInteger(0xdfff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe true
}
fun createTestProgramForMemoryRefViaVar(address: UInt, vartype: VarDeclType): AssignTarget {
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
module.linkIntoProgram(program)
wrapWithProgram(listOf(decl, assignment))
return target
}
test("testInValidRamC64_memory_identifiers") {
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
var target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.VAR)
target.isInRegularRAMof(C64Target.machine) shouldBe true
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.VAR)
target.isInRegularRAMof(C64Target.machine) shouldBe false
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.CONST)
target.isInRegularRAMof(C64Target.machine) shouldBe true
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.CONST)
target.isInRegularRAMof(C64Target.machine) shouldBe false
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.MEMORY)
target.isInRegularRAMof(C64Target.machine) shouldBe false
test("identifier mapped to IO memory on C64") {
var target = createTestProgramForMemoryRefViaVar(0x1000u, VarDeclType.VAR)
target.isIOAddress(C64Target.machine) shouldBe false
target = createTestProgramForMemoryRefViaVar(0xd020u, VarDeclType.VAR)
target.isIOAddress(C64Target.machine) shouldBe false
target = createTestProgramForMemoryRefViaVar(0x1000u, VarDeclType.CONST)
target.isIOAddress(C64Target.machine) shouldBe false
target = createTestProgramForMemoryRefViaVar(0xd020u, VarDeclType.CONST)
target.isIOAddress(C64Target.machine) shouldBe true
target = createTestProgramForMemoryRefViaVar(0x1000u, VarDeclType.MEMORY)
target.isIOAddress(C64Target.machine) shouldBe false
target = createTestProgramForMemoryRefViaVar(0xd020u, VarDeclType.MEMORY)
target.isIOAddress(C64Target.machine) shouldBe true
}
test("testInValidRamC64_memory_expression") {
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
target.isInRegularRAMof(C64Target.machine) shouldBe false
test("memory expression mapped to IO memory on C64") {
var memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
target.isIOAddress(C64Target.machine) shouldBe false
memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0xd020, Position.DUMMY), Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
wrapWithProgram(listOf(assign))
printProgram(target.definingModule.program)
target.isIOAddress(C64Target.machine) shouldBe true
}
test("testInValidRamC64_variable") {
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
test("regular variable not in mapped IO ram on C64") {
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, null, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
target.isInRegularRAMof(C64Target.machine) shouldBe true
target.isIOAddress(C64Target.machine) shouldBe false
}
test("testInValidRamC64_memmap_variable") {
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
test("memory mapped variable not in mapped IO ram on C64") {
val address = 0x1000u
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
target.isInRegularRAMof(C64Target.machine) shouldBe true
target.isIOAddress(C64Target.machine) shouldBe false
}
test("testNotInValidRamC64_memmap_variable") {
val address = 0xd020
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
test("memory mapped variable in mapped IO ram on C64") {
val address = 0xd020u
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
target.isInRegularRAMof(C64Target.machine) shouldBe false
target.isIOAddress(C64Target.machine) shouldBe true
}
test("testInValidRamC64_array") {
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
test("array not in mapped IO ram") {
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, null, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
target.isInRegularRAMof(C64Target.machine) shouldBe true
target.isIOAddress(C64Target.machine) shouldBe false
}
test("testInValidRamC64_array_memmapped") {
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
test("memory mapped array not in mapped IO ram") {
val address = 0x1000u
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
target.isInRegularRAMof(C64Target.machine) shouldBe true
target.isIOAddress(C64Target.machine) shouldBe false
}
test("testNotValidRamC64_array_memmapped") {
val address = 0xe000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
test("memory mapped array in mapped IO ram") {
val address = 0xd800u
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
.addModule(module)
module.linkIntoProgram(program)
target.isInRegularRAMof(C64Target.machine) shouldBe false
target.isIOAddress(C64Target.machine) shouldBe true
}
})
+50 -50
View File
@@ -44,37 +44,37 @@ class TestNumbers: FunSpec({
}
test("testFloatToMflpt5") {
Mflpt5.fromNumber(0) shouldBe Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(3.141592653) shouldBe Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1)
Mflpt5.fromNumber(3.141592653589793) shouldBe Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2)
Mflpt5.fromNumber(32768) shouldBe Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(-32768) shouldBe Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(1) shouldBe Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(0.7071067812) shouldBe Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34)
Mflpt5.fromNumber(0.7071067811865476) shouldBe Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33)
Mflpt5.fromNumber(1.4142135624) shouldBe Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34)
Mflpt5.fromNumber(1.4142135623730951) shouldBe Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33)
Mflpt5.fromNumber(-.5) shouldBe Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(0.69314718061) shouldBe Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8)
Mflpt5.fromNumber(0.6931471805599453) shouldBe Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7)
Mflpt5.fromNumber(10) shouldBe Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(1000000000) shouldBe Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00)
Mflpt5.fromNumber(.5) shouldBe Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(1.4426950408889634) shouldBe Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29)
Mflpt5.fromNumber(1.5707963267948966) shouldBe Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2)
Mflpt5.fromNumber(6.283185307179586) shouldBe Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2)
Mflpt5.fromNumber(.25) shouldBe Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(123.45678e22) shouldBe Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb)
Mflpt5.fromNumber(-123.45678e-22) shouldBe Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b)
Mflpt5.fromNumber(0) shouldBe Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(3.141592653) shouldBe Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA1u)
Mflpt5.fromNumber(3.141592653589793) shouldBe Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA2u)
Mflpt5.fromNumber(32768) shouldBe Mflpt5(0x90u, 0x00u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(-32768) shouldBe Mflpt5(0x90u, 0x80u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(1) shouldBe Mflpt5(0x81u, 0x00u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(0.7071067812) shouldBe Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x34u)
Mflpt5.fromNumber(0.7071067811865476) shouldBe Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x33u)
Mflpt5.fromNumber(1.4142135624) shouldBe Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x34u)
Mflpt5.fromNumber(1.4142135623730951) shouldBe Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x33u)
Mflpt5.fromNumber(-.5) shouldBe Mflpt5(0x80u, 0x80u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(0.69314718061) shouldBe Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF8u)
Mflpt5.fromNumber(0.6931471805599453) shouldBe Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF7u)
Mflpt5.fromNumber(10) shouldBe Mflpt5(0x84u, 0x20u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(1000000000) shouldBe Mflpt5(0x9Eu, 0x6Eu, 0x6Bu, 0x28u, 0x00u)
Mflpt5.fromNumber(.5) shouldBe Mflpt5(0x80u, 0x00u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(1.4426950408889634) shouldBe Mflpt5(0x81u, 0x38u, 0xAAu, 0x3Bu, 0x29u)
Mflpt5.fromNumber(1.5707963267948966) shouldBe Mflpt5(0x81u, 0x49u, 0x0Fu, 0xDAu, 0xA2u)
Mflpt5.fromNumber(6.283185307179586) shouldBe Mflpt5(0x83u, 0x49u, 0x0Fu, 0xDAu, 0xA2u)
Mflpt5.fromNumber(.25) shouldBe Mflpt5(0x7Fu, 0x00u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(123.45678e22) shouldBe Mflpt5(0xd1u, 0x02u, 0xb7u, 0x06u, 0xfbu)
Mflpt5.fromNumber(-123.45678e-22) shouldBe Mflpt5(0x3eu, 0xe9u, 0x34u, 0x09u, 0x1bu)
}
test("testFloatRange") {
Mflpt5.fromNumber(FLOAT_MAX_POSITIVE) shouldBe Mflpt5(0xff, 0x7f, 0xff, 0xff, 0xff)
Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE) shouldBe Mflpt5(0xff, 0xff, 0xff, 0xff, 0xff)
Mflpt5.fromNumber(1.7e-38) shouldBe Mflpt5(0x03, 0x39, 0x1d, 0x15, 0x63)
Mflpt5.fromNumber(1.7e-39) shouldBe Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(-1.7e-38) shouldBe Mflpt5(0x03, 0xb9, 0x1d, 0x15, 0x63)
Mflpt5.fromNumber(-1.7e-39) shouldBe Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)
Mflpt5.fromNumber(FLOAT_MAX_POSITIVE) shouldBe Mflpt5(0xffu, 0x7fu, 0xffu, 0xffu, 0xffu)
Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE) shouldBe Mflpt5(0xffu, 0xffu, 0xffu, 0xffu, 0xffu)
Mflpt5.fromNumber(1.7e-38) shouldBe Mflpt5(0x03u, 0x39u, 0x1du, 0x15u, 0x63u)
Mflpt5.fromNumber(1.7e-39) shouldBe Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
Mflpt5.fromNumber(-1.7e-38) shouldBe Mflpt5(0x03u, 0xb9u, 0x1du, 0x15u, 0x63u)
Mflpt5.fromNumber(-1.7e-39) shouldBe Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
@@ -84,27 +84,27 @@ class TestNumbers: FunSpec({
test("testMflpt5ToFloat") {
val epsilon=0.000000001
Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00).toDouble() shouldBe 0.0
Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1).toDouble() shouldBe(3.141592653 plusOrMinus epsilon)
Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2).toDouble() shouldBe(3.141592653589793 plusOrMinus epsilon)
Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00).toDouble() shouldBe 32768.0
Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00).toDouble() shouldBe -32768.0
Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00).toDouble() shouldBe 1.0
Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34).toDouble() shouldBe(0.7071067812 plusOrMinus epsilon)
Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33).toDouble() shouldBe(0.7071067811865476 plusOrMinus epsilon)
Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34).toDouble() shouldBe(1.4142135624 plusOrMinus epsilon)
Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33).toDouble() shouldBe(1.4142135623730951 plusOrMinus epsilon)
Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00).toDouble() shouldBe -.5
Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8).toDouble() shouldBe(0.69314718061 plusOrMinus epsilon)
Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7).toDouble() shouldBe(0.6931471805599453 plusOrMinus epsilon)
Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00).toDouble() shouldBe 10.0
Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00).toDouble() shouldBe 1000000000.0
Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00).toDouble() shouldBe .5
Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29).toDouble() shouldBe(1.4426950408889634 plusOrMinus epsilon)
Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2).toDouble() shouldBe(1.5707963267948966 plusOrMinus epsilon)
Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2).toDouble() shouldBe(6.283185307179586 plusOrMinus epsilon)
Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00).toDouble() shouldBe .25
Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb).toDouble() shouldBe(123.45678e22 plusOrMinus 1.0e15)
Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b).toDouble() shouldBe(-123.45678e-22 plusOrMinus epsilon)
Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 0.0
Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA1u).toDouble() shouldBe(3.141592653 plusOrMinus epsilon)
Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA2u).toDouble() shouldBe(3.141592653589793 plusOrMinus epsilon)
Mflpt5(0x90u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 32768.0
Mflpt5(0x90u, 0x80u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe -32768.0
Mflpt5(0x81u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 1.0
Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x34u).toDouble() shouldBe(0.7071067812 plusOrMinus epsilon)
Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x33u).toDouble() shouldBe(0.7071067811865476 plusOrMinus epsilon)
Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x34u).toDouble() shouldBe(1.4142135624 plusOrMinus epsilon)
Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x33u).toDouble() shouldBe(1.4142135623730951 plusOrMinus epsilon)
Mflpt5(0x80u, 0x80u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe -.5
Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF8u).toDouble() shouldBe(0.69314718061 plusOrMinus epsilon)
Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF7u).toDouble() shouldBe(0.6931471805599453 plusOrMinus epsilon)
Mflpt5(0x84u, 0x20u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 10.0
Mflpt5(0x9Eu, 0x6Eu, 0x6Bu, 0x28u, 0x00u).toDouble() shouldBe 1000000000.0
Mflpt5(0x80u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe .5
Mflpt5(0x81u, 0x38u, 0xAAu, 0x3Bu, 0x29u).toDouble() shouldBe(1.4426950408889634 plusOrMinus epsilon)
Mflpt5(0x81u, 0x49u, 0x0Fu, 0xDAu, 0xA2u).toDouble() shouldBe(1.5707963267948966 plusOrMinus epsilon)
Mflpt5(0x83u, 0x49u, 0x0Fu, 0xDAu, 0xA2u).toDouble() shouldBe(6.283185307179586 plusOrMinus epsilon)
Mflpt5(0x7Fu, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe .25
Mflpt5(0xd1u, 0x02u, 0xb7u, 0x06u, 0xfbu).toDouble() shouldBe(123.45678e22 plusOrMinus 1.0e15)
Mflpt5(0x3eu, 0xe9u, 0x34u, 0x09u, 0x1bu).toDouble() shouldBe(-123.45678e-22 plusOrMinus epsilon)
}
})
+76 -57
View File
@@ -1,9 +1,12 @@
package prog8tests
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.ast.base.DataType
import prog8.ast.base.ExpressionError
import prog8.ast.base.Position
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.InferredTypes
@@ -19,7 +22,7 @@ class TestNumericLiteralValue: FunSpec({
val dummyPos = Position("test", 0, 0, 0)
test("testIdentity") {
val v = NumericLiteralValue(DataType.UWORD, 12345, dummyPos)
val v = NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)
(v==v) shouldBe true
(v != v) shouldBe false
(v <= v) shouldBe true
@@ -27,48 +30,64 @@ class TestNumericLiteralValue: FunSpec({
(v < v ) shouldBe false
(v > v ) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)) shouldBe true
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)) shouldBe true
}
test("test rounding") {
shouldThrow<ExpressionError> {
NumericLiteralValue(DataType.BYTE, -2.345, dummyPos)
}.message shouldContain "refused silent rounding"
shouldThrow<ExpressionError> {
NumericLiteralValue(DataType.BYTE, -2.6, dummyPos)
}.message shouldContain "refused silent rounding"
shouldThrow<ExpressionError> {
NumericLiteralValue(DataType.UWORD, 2222.345, dummyPos)
}.message shouldContain "refused silent rounding"
NumericLiteralValue(DataType.UBYTE, 2.0, dummyPos).number shouldBe 2.0
NumericLiteralValue(DataType.BYTE, -2.0, dummyPos).number shouldBe -2.0
NumericLiteralValue(DataType.UWORD, 2222.0, dummyPos).number shouldBe 2222.0
NumericLiteralValue(DataType.FLOAT, 123.456, dummyPos)
}
test("testEqualsAndNotEquals") {
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) == NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) == NumericLiteralValue(DataType.UWORD, 100, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) == NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) == NumericLiteralValue(DataType.UBYTE, 254, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345, dummyPos) == NumericLiteralValue(DataType.UWORD, 12345, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345, dummyPos) == NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos) == NumericLiteralValue(DataType.UWORD, 22239, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) == NumericLiteralValue(DataType.UWORD, 100.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) == NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 254.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) == NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) == NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos) == NumericLiteralValue(DataType.UWORD, 22239.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) == NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos)) shouldBe true
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe true
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)) shouldBe true
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UWORD, 100.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 254.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)) shouldBe true
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) != NumericLiteralValue(DataType.UBYTE, 101, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) != NumericLiteralValue(DataType.UWORD, 101, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) != NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 245, dummyPos) != NumericLiteralValue(DataType.UBYTE, 246, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345, dummyPos) != NumericLiteralValue(DataType.UWORD, 12346, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345, dummyPos) != NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.UBYTE, 9, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.UWORD, 9, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) != NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) != NumericLiteralValue(DataType.UWORD, 101.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) != NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 245.0, dummyPos) != NumericLiteralValue(DataType.UBYTE, 246.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) != NumericLiteralValue(DataType.UWORD, 12346.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) != NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.UBYTE, 9.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.UWORD, 9.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos)) shouldBe true
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 101, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UWORD, 101.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 245.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 246.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.UWORD, 12346.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9.0, dummyPos)) shouldBe false
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos)) shouldBe false
@@ -81,13 +100,13 @@ class TestNumericLiteralValue: FunSpec({
(StringLiteralValue("hello", true, dummyPos) != StringLiteralValue("bye", true, dummyPos)) shouldBe true
(StringLiteralValue("hello", true, dummyPos) != StringLiteralValue("hello", false, dummyPos)) shouldBe true
val lvOne = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
val lvThree = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
val lvOneR = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
val lvFour= NumericLiteralValue(DataType.UBYTE, 4, dummyPos)
val lvOne = NumericLiteralValue(DataType.UBYTE, 1.0, dummyPos)
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2.0, dummyPos)
val lvThree = NumericLiteralValue(DataType.UBYTE, 3.0, dummyPos)
val lvOneR = NumericLiteralValue(DataType.UBYTE, 1.0, dummyPos)
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2.0, dummyPos)
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3.0, dummyPos)
val lvFour= NumericLiteralValue(DataType.UBYTE, 4.0, dummyPos)
val lv1 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOne, lvTwo, lvThree), dummyPos)
val lv2 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOneR, lvTwoR, lvThreeR), dummyPos)
val lv3 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOneR, lvTwoR, lvFour), dummyPos)
@@ -96,38 +115,38 @@ class TestNumericLiteralValue: FunSpec({
}
test("testGreaterThan") {
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 99, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 253, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) > NumericLiteralValue(DataType.UBYTE, 99.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) > NumericLiteralValue(DataType.UWORD, 253.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 254, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) >= NumericLiteralValue(DataType.UWORD, 254.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 254, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) > NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) > NumericLiteralValue(DataType.UWORD, 254.0, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 101, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 255, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) >= NumericLiteralValue(DataType.UWORD, 255.0, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos)) shouldBe false
}
test("testLessThan") {
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 101, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 255, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) < NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) < NumericLiteralValue(DataType.UWORD, 255.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 254, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) <= NumericLiteralValue(DataType.UWORD, 254.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 100, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 254, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) < NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) < NumericLiteralValue(DataType.UWORD, 254.0, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 99, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 253, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 99.0, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) <= NumericLiteralValue(DataType.UWORD, 253.0, dummyPos)) shouldBe false
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos)) shouldBe false
}
+266 -21
View File
@@ -5,6 +5,7 @@ import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.instanceOf
import io.kotest.matchers.types.shouldBeSameInstanceAs
import prog8.ast.Program
@@ -14,9 +15,10 @@ import prog8.ast.base.Position
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.compiler.printAst
import prog8.compiler.printProgram
import prog8.compiler.target.C64Target
import prog8.compilerinterface.*
import prog8tests.helpers.*
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
@@ -26,7 +28,7 @@ import prog8tests.helpers.compileText
import prog8tests.helpers.generateAssembly
class TestOptimization: FunSpec({
test("testRemoveEmptySubroutineExceptStart") {
test("remove empty subroutine except start") {
val sourcecode = """
main {
sub start() {
@@ -49,7 +51,7 @@ class TestOptimization: FunSpec({
}
}
test("testDontRemoveEmptySubroutineIfItsReferenced") {
test("don't remove empty subroutine if it's referenced") {
val sourcecode = """
main {
sub start() {
@@ -74,8 +76,8 @@ class TestOptimization: FunSpec({
}
}
test("testGeneratedConstvalueInheritsProperParentLinkage") {
val number = NumericLiteralValue(DataType.UBYTE, 11, Position.DUMMY)
test("generated constvalue from typecast inherits proper parent linkage") {
val number = NumericLiteralValue(DataType.UBYTE, 11.0, Position.DUMMY)
val tc = TypecastExpression(number, DataType.BYTE, false, Position.DUMMY)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
tc.linkParents(ParentSentinel)
@@ -84,12 +86,27 @@ class TestOptimization: FunSpec({
tc shouldBeSameInstanceAs number.parent
val constvalue = tc.constValue(program)!!
constvalue shouldBe instanceOf<NumericLiteralValue>()
constvalue.number.toInt() shouldBe 11
constvalue.number shouldBe 11.0
constvalue.type shouldBe DataType.BYTE
tc shouldBeSameInstanceAs constvalue.parent
constvalue.parent shouldBeSameInstanceAs tc.parent
}
test("testConstantFoldedAndSilentlyTypecastedForInitializerValues") {
test("generated constvalue from prefixexpr inherits proper parent linkage") {
val number = NumericLiteralValue(DataType.UBYTE, 11.0, Position.DUMMY)
val pfx = PrefixExpression("-", number, Position.DUMMY)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
pfx.linkParents(ParentSentinel)
pfx.parent shouldNotBe null
number.parent shouldNotBe null
pfx shouldBeSameInstanceAs number.parent
val constvalue = pfx.constValue(program)!!
constvalue shouldBe instanceOf<NumericLiteralValue>()
constvalue.number shouldBe -11.0
constvalue.type shouldBe DataType.BYTE
constvalue.parent shouldBeSameInstanceAs pfx.parent
}
test("constantfolded and silently typecasted for initializervalues") {
val sourcecode = """
main {
sub start() {
@@ -114,19 +131,19 @@ class TestOptimization: FunSpec({
val declY2 = mainsub.statements[7] as VarDecl
val initY2 = mainsub.statements[8] as Assignment
mainsub.statements[9] shouldBe instanceOf<Return>()
(declTest.value as NumericLiteralValue).number.toDouble() shouldBe 10.0
(declTest.value as NumericLiteralValue).number shouldBe 10.0
declX1.value shouldBe null
declX2.value shouldBe null
declY1.value shouldBe null
declY2.value shouldBe null
(initX1.value as NumericLiteralValue).type shouldBe DataType.BYTE
(initX1.value as NumericLiteralValue).number.toDouble() shouldBe 11.0
(initX1.value as NumericLiteralValue).number shouldBe 11.0
(initX2.value as NumericLiteralValue).type shouldBe DataType.BYTE
(initX2.value as NumericLiteralValue).number.toDouble() shouldBe 11.0
(initX2.value as NumericLiteralValue).number shouldBe 11.0
(initY1.value as NumericLiteralValue).type shouldBe DataType.UBYTE
(initY1.value as NumericLiteralValue).number.toDouble() shouldBe 11.0
(initY1.value as NumericLiteralValue).number shouldBe 11.0
(initY2.value as NumericLiteralValue).type shouldBe DataType.UBYTE
(initY2.value as NumericLiteralValue).number.toDouble() shouldBe 11.0
(initY2.value as NumericLiteralValue).number shouldBe 11.0
}
test("typecasted assignment from ubyte logical expressoin to uword var") {
@@ -250,22 +267,21 @@ class TestOptimization: FunSpec({
main {
sub start() {
ubyte ub
float ff
float ff = 1.0
ff += (ub as float) ; operator doesn't matter
}
}
"""
val result1 = compileText(C64Target, optimize=false, src, writeAssembly = false).assertSuccess()
val assignYY = result1.program.entrypoint.statements.last() as Assignment
assignYY.isAugmentable shouldBe true
assignYY.target.identifier!!.nameInSource shouldBe listOf("ff")
val value = assignYY.value as BinaryExpression
val result = compileText(C64Target, optimize=false, src, writeAssembly = false).assertSuccess()
val assignFF = result.program.entrypoint.statements.last() as Assignment
assignFF.isAugmentable shouldBe true
assignFF.target.identifier!!.nameInSource shouldBe listOf("ff")
val value = assignFF.value as BinaryExpression
value.operator shouldBe "+"
value.left shouldBe IdentifierReference(listOf("ff"), Position.DUMMY)
value.right shouldBe instanceOf<TypecastExpression>()
val asm = generateAssembly(result1.program)
val asm = generateAssembly(result.program)
asm.valid shouldBe true
}
@@ -321,4 +337,233 @@ class TestOptimization: FunSpec({
func2.statements.size shouldBe 1
(func2.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
}
test("test simple augmented assignment optimization correctly initializes all variables") {
val src="""
main {
sub start() {
ubyte @shared z1
z1 = 10
ubyte @shared z2
z2 = ~z2
ubyte @shared z3
z3 = not z3
uword @shared z4
z4 = (z4 as ubyte)
ubyte @shared z5
z5 = z1+z5+5
ubyte @shared z6
z6 = z1+z6-5
}
}"""
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
/* expected:
ubyte z1
z1 = 10
ubyte z2
z2 = 255
ubyte z3
z3 = 1
uword z4
z4 = 0
ubyte z5
z5 = z1
z5 += 5
ubyte z6
z6 = z1
z6 -= 5
*/
val statements = result.program.entrypoint.statements
statements.size shouldBe 14
val z1decl = statements[0] as VarDecl
val z1init = statements[1] as Assignment
val z2decl = statements[2] as VarDecl
val z2init = statements[3] as Assignment
val z3decl = statements[4] as VarDecl
val z3init = statements[5] as Assignment
val z4decl = statements[6] as VarDecl
val z4init = statements[7] as Assignment
val z5decl = statements[8] as VarDecl
val z5init = statements[9] as Assignment
val z5plus = statements[10] as Assignment
val z6decl = statements[11] as VarDecl
val z6init = statements[12] as Assignment
val z6plus = statements[13] as Assignment
z1decl.name shouldBe "z1"
z1init.value shouldBe NumericLiteralValue(DataType.UBYTE, 10.0, Position.DUMMY)
z2decl.name shouldBe "z2"
z2init.value shouldBe NumericLiteralValue(DataType.UBYTE, 255.0, Position.DUMMY)
z3decl.name shouldBe "z3"
z3init.value shouldBe NumericLiteralValue(DataType.UBYTE, 1.0, Position.DUMMY)
z4decl.name shouldBe "z4"
z4init.value shouldBe NumericLiteralValue(DataType.UBYTE, 0.0, Position.DUMMY)
z5decl.name shouldBe "z5"
z5init.value shouldBe IdentifierReference(listOf("z1"), Position.DUMMY)
z5plus.isAugmentable shouldBe true
(z5plus.value as BinaryExpression).operator shouldBe "+"
(z5plus.value as BinaryExpression).right shouldBe NumericLiteralValue(DataType.UBYTE, 5.0, Position.DUMMY)
z6decl.name shouldBe "z6"
z6init.value shouldBe IdentifierReference(listOf("z1"), Position.DUMMY)
z6plus.isAugmentable shouldBe true
(z6plus.value as BinaryExpression).operator shouldBe "-"
(z6plus.value as BinaryExpression).right shouldBe NumericLiteralValue(DataType.UBYTE, 5.0, Position.DUMMY)
}
test("force_output option should work with optimizing memwrite assignment") {
val src="""
main {
%option force_output
sub start() {
uword aa
ubyte zz
@(aa) = zz + 32
}
}
"""
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 6
val assign=stmts.last() as Assignment
(assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
}
test("don't optimize memory writes away") {
val src="""
main {
sub start() {
uword aa
ubyte zz
@(aa) = zz + 32 ; do not optimize this away!
}
}
"""
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 6
val assign=stmts.last() as Assignment
(assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
}
test("correctly process constant prefix numbers") {
val src="""
main {
sub start() {
ubyte @shared z1 = 1
ubyte @shared z2 = + 1
ubyte @shared z3 = ~ 1
ubyte @shared z4 = not 1
byte @shared z5 = - 1
}
}
"""
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 10
stmts.filterIsInstance<VarDecl>().size shouldBe 5
stmts.filterIsInstance<Assignment>().size shouldBe 5
}
test("correctly process constant prefix numbers with type mismatch and give error") {
val src="""
main {
sub start() {
ubyte @shared z1 = - 1
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target, optimize=true, src, writeAssembly=false, errors = errors).assertFailure()
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "type of value BYTE doesn't match target UBYTE"
errors.errors[1] shouldContain "value '-1' out of range for unsigned byte"
}
test("test augmented expression asmgen") {
val src = """
main {
sub start() {
ubyte c
ubyte r
ubyte q
r = (q+r)-c
q=r
r = q+(r-c)
q=r
}
}"""
val result = compileText(C64Target, optimize=false, src, writeAssembly=true).assertSuccess()
result.program.entrypoint.statements.size shouldBe 11
result.program.entrypoint.statements.last() shouldBe instanceOf<Return>()
}
test("keep the value initializer assignment if the next one depends on it") {
val src="""
main {
sub start() {
uword @shared yy
yy = 20 ; ok to remove =0 initializer before this
uword @shared zz
zz += 60 ; NOT ok to remove initializer, should evaluate to 60
ubyte @shared xx
xx = 6+sin8u(xx) ; NOT ok to remove initializer
}
}
"""
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
/* expected result:
uword yy
yy = 20
uword zz
zz = 60
ubyte xx
xx = 0
xx = sin8u(xx)
xx += 6
*/
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 8
stmts.filterIsInstance<VarDecl>().size shouldBe 3
stmts.filterIsInstance<Assignment>().size shouldBe 5
}
test("only substitue assignments with 0 after a =0 initializer if it is the same variable") {
val src="""
main {
sub start() {
uword @shared xx
xx = xx + 20 ; is same var so can be changed just fine into xx=20
uword @shared yy
xx = 20
yy = 0 ; is other var..
xx = xx+10 ; so this should not be changed into xx=10
}
}"""
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
/*
expected result:
uword xx
xx = 20
uword yy
yy = 0
xx = 20
yy = 0
xx += 10
*/
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 7
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 NumericLiteralValue(DataType.UBYTE, 20.0, Position.DUMMY)
val assignXX2 = stmts.last() as Assignment
assignXX2.target.identifier!!.nameInSource shouldBe listOf("xx")
val xxValue = assignXX2.value as BinaryExpression
xxValue.operator shouldBe "+"
xxValue.left shouldBe IdentifierReference(listOf("xx"), Position.DUMMY)
xxValue.right shouldBe NumericLiteralValue(DataType.UBYTE, 10.0, Position.DUMMY)
}
})
+92 -110
View File
@@ -3,73 +3,71 @@ package prog8tests
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.expectError
import com.github.michaelbull.result.getOrElse
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.compiler.target.cbm.Petscii
import java.io.CharConversionException
class TestPetscii: FunSpec({
test("testZero") {
Petscii.encodePetscii("\u0000", true) shouldBe Ok(listOf<Short>(0))
Petscii.encodePetscii("\u0000", false) shouldBe Ok(listOf<Short>(0))
Petscii.decodePetscii(listOf(0), true) shouldBe "\u0000"
Petscii.decodePetscii(listOf(0), false) shouldBe "\u0000"
Petscii.encodePetscii("\u0000", true) shouldBe Ok(listOf<UByte>(0u))
Petscii.encodePetscii("\u0000", false) shouldBe Ok(listOf<UByte>(0u))
Petscii.decodePetscii(listOf(0u), true) shouldBe "\u0000"
Petscii.decodePetscii(listOf(0u), false) shouldBe "\u0000"
}
test("testLowercase") {
Petscii.encodePetscii("hello WORLD 123 @!£", true) shouldBe
Ok(listOf<Short>(72, 69, 76, 76, 79, 32, 0xd7, 0xcf, 0xd2, 0xcc, 0xc4, 32, 49, 50, 51, 32, 64, 33, 0x5c))
Petscii.encodePetscii("\uf11a", true) shouldBe Ok(listOf<Short>(0x12)) // reverse vid
Petscii.encodePetscii("✓", true) shouldBe Ok(listOf<Short>(0xfa))
Ok(listOf<UByte>(72u, 69u, 76u, 76u, 79u, 32u, 0xd7u, 0xcfu, 0xd2u, 0xccu, 0xc4u, 32u, 49u, 50u, 51u, 32u, 64u, 33u, 0x5cu))
Petscii.encodePetscii("\uf11a", true) shouldBe Ok(listOf<UByte>(0x12u)) // reverse vid
Petscii.encodePetscii("✓", true) shouldBe Ok(listOf<UByte>(0xfau))
withClue("expect lowercase error fallback") {
Petscii.encodePetscii("π", true) shouldBe Ok(listOf<Short>(255))
Petscii.encodePetscii("♥", true) shouldBe Ok(listOf<Short>(0xd3))
Petscii.encodePetscii("π", true) shouldBe Ok(listOf<UByte>(255u))
Petscii.encodePetscii("♥", true) shouldBe Ok(listOf<UByte>(0xd3u))
}
Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true) shouldBe "hW£✓\uF11A"
Petscii.decodePetscii(listOf(72u, 0xd7u, 0x5cu, 0xfau, 0x12u), true) shouldBe "hW£✓\uF11A"
}
test("testUppercase") {
Petscii.encodePetscii("HELLO 123 @!£") shouldBe
Ok(listOf<Short>(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c))
Petscii.encodePetscii("\uf11a") shouldBe Ok(listOf<Short>(0x12)) // reverse vid
Petscii.encodePetscii("♥") shouldBe Ok(listOf<Short>(0xd3))
Petscii.encodePetscii("π") shouldBe Ok(listOf<Short>(0xff))
Ok(listOf<UByte>(72u, 69u, 76u, 76u, 79u, 32u, 49u, 50u, 51u, 32u, 64u, 33u, 0x5cu))
Petscii.encodePetscii("\uf11a") shouldBe Ok(listOf<UByte>(0x12u)) // reverse vid
Petscii.encodePetscii("♥") shouldBe Ok(listOf<UByte>(0xd3u))
Petscii.encodePetscii("π") shouldBe Ok(listOf<UByte>(0xffu))
withClue("expecting fallback") {
Petscii.encodePetscii("✓") shouldBe Ok(listOf<Short>(250))
Petscii.encodePetscii("✓") shouldBe Ok(listOf<UByte>(250u))
}
Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)) shouldBe "H£♥π"
Petscii.decodePetscii(listOf(72u, 0x5cu, 0xd3u, 0xffu)) shouldBe "H£♥π"
}
test("testScreencodeLowercase") {
Petscii.encodeScreencode("hello WORLD 123 @!£", true) shouldBe
Ok(listOf<Short>(0x08, 0x05, 0x0c, 0x0c, 0x0f, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))
Petscii.encodeScreencode("✓", true) shouldBe Ok(listOf<Short>(0x7a))
Ok(listOf<UByte>(0x08u, 0x05u, 0x0cu, 0x0cu, 0x0fu, 0x20u, 0x57u, 0x4fu, 0x52u, 0x4cu, 0x44u, 0x20u, 0x31u, 0x32u, 0x33u, 0x20u, 0x00u, 0x21u, 0x1cu))
Petscii.encodeScreencode("✓", true) shouldBe Ok(listOf<UByte>(0x7au))
withClue("expect fallback") {
Petscii.encodeScreencode("♥", true) shouldBe Ok(listOf<Short>(83))
Petscii.encodeScreencode("π", true) shouldBe Ok(listOf<Short>(94))
Petscii.encodeScreencode("♥", true) shouldBe Ok(listOf<UByte>(83u))
Petscii.encodeScreencode("π", true) shouldBe Ok(listOf<UByte>(94u))
}
Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true) shouldBe "hW£✓"
Petscii.decodeScreencode(listOf(0x08u, 0x57u, 0x1cu, 0x7au), true) shouldBe "hW£✓"
}
test("testScreencodeUppercase") {
Petscii.encodeScreencode("WORLD 123 @!£") shouldBe
Ok(listOf<Short>(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))
Petscii.encodeScreencode("♥") shouldBe Ok(listOf<Short>(0x53))
Petscii.encodeScreencode("π") shouldBe Ok(listOf<Short>(0x5e))
Petscii.encodeScreencode("HELLO") shouldBe Ok(listOf<Short>(8, 5, 12, 12, 15))
Ok(listOf<UByte>(0x17u, 0x0fu, 0x12u, 0x0cu, 0x04u, 0x20u, 0x31u, 0x32u, 0x33u, 0x20u, 0x00u, 0x21u, 0x1cu))
Petscii.encodeScreencode("♥") shouldBe Ok(listOf<UByte>(0x53u))
Petscii.encodeScreencode("π") shouldBe Ok(listOf<UByte>(0x5eu))
Petscii.encodeScreencode("HELLO") shouldBe Ok(listOf<UByte>(8u, 5u, 12u, 12u, 15u))
withClue("expecting fallback") {
Petscii.encodeScreencode("hello") shouldBe Ok(listOf<Short>(8, 5, 12, 12, 15))
Petscii.encodeScreencode("✓") shouldBe Ok(listOf<Short>(122))
Petscii.encodeScreencode("hello") shouldBe Ok(listOf<UByte>(8u, 5u, 12u, 12u, 15u))
Petscii.encodeScreencode("✓") shouldBe Ok(listOf<UByte>(122u))
}
Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)) shouldBe "W£♥π"
Petscii.decodeScreencode(listOf(0x17u, 0x1cu, 0x53u, 0x5eu)) shouldBe "W£♥π"
}
test("testErrorCases") {
@@ -77,22 +75,6 @@ class TestPetscii: FunSpec({
Petscii.encodePetscii("~", false).expectError { "shouldn't be able to encode tilde" }
Petscii.encodeScreencode("~", true).expectError { "shouldn't be able to encode tilde" }
Petscii.encodeScreencode("~", false).expectError { "shouldn't be able to encode tilde" }
shouldThrow<CharConversionException> { Petscii.decodePetscii(listOf<Short>(-1), true) }
shouldThrow<CharConversionException> { Petscii.decodePetscii(listOf<Short>(256), true) }
shouldThrow<CharConversionException> { Petscii.decodePetscii(listOf<Short>(-1), false) }
shouldThrow<CharConversionException> { Petscii.decodePetscii(listOf<Short>(256), false) }
shouldThrow<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(-1), true) }
shouldThrow<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(256), true) }
shouldThrow<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(-1), false) }
shouldThrow<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(256), false) }
Petscii.scr2petscii(-1).expectError { "-1 should error" }
Petscii.scr2petscii(256).expectError { "256 should error" }
Petscii.petscii2scr(-1, true).expectError { "-1 should error" }
Petscii.petscii2scr(256, true).expectError { "256 should error" }
Petscii.petscii2scr(-1, false).expectError { "-1 should error" }
Petscii.petscii2scr(256, false).expectError { "256 should error" }
}
test("testSpecialReplacements") {
@@ -104,30 +86,30 @@ class TestPetscii: FunSpec({
Petscii.encodePetscii("~", false).expectError { "shouldn't have translation for tilde" }
Petscii.encodePetscii("~", true).expectError { "shouldn't have translation for tilde" }
encodeP('^', false) shouldBe 94
encodeP('^', true) shouldBe 94
encodeS('^', false) shouldBe 30
encodeS('^', true) shouldBe 30
encodeP('_', false) shouldBe 228
encodeP('_', true) shouldBe 228
encodeS('_', false) shouldBe 100
encodeS('_', true) shouldBe 100
encodeP('{', false) shouldBe 243
encodeP('{', true) shouldBe 243
encodeS('{', false) shouldBe 115
encodeS('{', true) shouldBe 115
encodeP('}', false) shouldBe 235
encodeP('}', true) shouldBe 235
encodeS('}', false) shouldBe 107
encodeS('}', true) shouldBe 107
encodeP('|', false) shouldBe 221
encodeP('|', true) shouldBe 221
encodeS('|', false) shouldBe 93
encodeS('|', true) shouldBe 93
encodeP('\\', false) shouldBe 205
encodeP('\\', true) shouldBe 205
encodeS('\\', false) shouldBe 77
encodeS('\\', true) shouldBe 77
encodeP('^', false) shouldBe 94u
encodeP('^', true) shouldBe 94u
encodeS('^', false) shouldBe 30u
encodeS('^', true) shouldBe 30u
encodeP('_', false) shouldBe 228u
encodeP('_', true) shouldBe 228u
encodeS('_', false) shouldBe 100u
encodeS('_', true) shouldBe 100u
encodeP('{', false) shouldBe 243u
encodeP('{', true) shouldBe 243u
encodeS('{', false) shouldBe 115u
encodeS('{', true) shouldBe 115u
encodeP('}', false) shouldBe 235u
encodeP('}', true) shouldBe 235u
encodeS('}', false) shouldBe 107u
encodeS('}', true) shouldBe 107u
encodeP('|', false) shouldBe 221u
encodeP('|', true) shouldBe 221u
encodeS('|', false) shouldBe 93u
encodeS('|', true) shouldBe 93u
encodeP('\\', false) shouldBe 205u
encodeP('\\', true) shouldBe 205u
encodeS('\\', false) shouldBe 77u
encodeS('\\', true) shouldBe 77u
}
test("testBoxDrawingCharsEncoding") {
@@ -135,59 +117,59 @@ class TestPetscii: FunSpec({
fun encodeS(c: Char, lower: Boolean) = Petscii.encodeScreencode(c.toString(), lower).getOrElse { throw it }.single()
// pipe char
encodeP('|', false) shouldBe 221
encodeP('|', true) shouldBe 221
encodeS('|', false) shouldBe 93
encodeS('|', true) shouldBe 93
encodeP('|', false) shouldBe 221u
encodeP('|', true) shouldBe 221u
encodeS('|', false) shouldBe 93u
encodeS('|', true) shouldBe 93u
// ... same as '│', 0x7D -> BOX DRAWINGS LIGHT VERTICAL
encodeP('│', false) shouldBe 221
encodeP('│', true) shouldBe 221
encodeS('│', false) shouldBe 93
encodeS('│', true) shouldBe 93
encodeP('│', false) shouldBe 221u
encodeP('│', true) shouldBe 221u
encodeS('│', false) shouldBe 93u
encodeS('│', true) shouldBe 93u
// underscore
encodeP('_', false) shouldBe 228
encodeP('_', true) shouldBe 228
encodeS('_', false) shouldBe 100
encodeS('_', true) shouldBe 100
encodeP('_', false) shouldBe 228u
encodeP('_', true) shouldBe 228u
encodeS('_', false) shouldBe 100u
encodeS('_', true) shouldBe 100u
// ... same as '▁', 0xE4 LOWER ONE EIGHTH BLOCK
encodeP('▁', false) shouldBe 228
encodeP('▁', true) shouldBe 228
encodeS('▁', false) shouldBe 100
encodeS('▁', true) shouldBe 100
encodeP('▁', false) shouldBe 228u
encodeP('▁', true) shouldBe 228u
encodeS('▁', false) shouldBe 100u
encodeS('▁', true) shouldBe 100u
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
encodeP('─', false) shouldBe 192
encodeP('─', true) shouldBe 192
encodeS('─', false) shouldBe 64
encodeS('─', true) shouldBe 64
encodeP('─', false) shouldBe 192u
encodeP('─', true) shouldBe 192u
encodeS('─', false) shouldBe 64u
encodeS('─', true) shouldBe 64u
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
encodeP('│', false) shouldBe 221
encodeP('│', true) shouldBe 221
encodeS('│', false) shouldBe 93
encodeS('│', true) shouldBe 93
encodeP('│', false) shouldBe 221u
encodeP('│', true) shouldBe 221u
encodeS('│', false) shouldBe 93u
encodeS('│', true) shouldBe 93u
}
test("testBoxDrawingCharsDecoding") {
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
Petscii.decodePetscii(listOf(195), false).single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
Petscii.decodePetscii(listOf(195), true).single() shouldBe 'C'
Petscii.decodePetscii(listOf(192), false).single() shouldBe '─'
Petscii.decodePetscii(listOf(192), true).single() shouldBe '─'
Petscii.decodeScreencode(listOf(67), false).single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
Petscii.decodeScreencode(listOf(67), true).single() shouldBe 'C'
Petscii.decodeScreencode(listOf(64), false).single() shouldBe '─'
Petscii.decodeScreencode(listOf(64), true).single() shouldBe '─'
Petscii.decodePetscii(listOf(195u), false).single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
Petscii.decodePetscii(listOf(195u), true).single() shouldBe 'C'
Petscii.decodePetscii(listOf(192u), false).single() shouldBe '─'
Petscii.decodePetscii(listOf(192u), true).single() shouldBe '─'
Petscii.decodeScreencode(listOf(67u), false).single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
Petscii.decodeScreencode(listOf(67u), true).single() shouldBe 'C'
Petscii.decodeScreencode(listOf(64u), false).single() shouldBe '─'
Petscii.decodeScreencode(listOf(64u), true).single() shouldBe '─'
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
Petscii.decodePetscii(listOf(125), false).single() shouldBe '│'
Petscii.decodePetscii(listOf(125), true).single() shouldBe '│'
Petscii.decodePetscii(listOf(221), false).single() shouldBe '│'
Petscii.decodePetscii(listOf(221), true).single() shouldBe '│'
Petscii.decodeScreencode(listOf(93), false).single() shouldBe '│'
Petscii.decodeScreencode(listOf(93), true).single() shouldBe '│'
Petscii.decodeScreencode(listOf(66), false).single() shouldBe '\uf13c' // "BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)"
Petscii.decodeScreencode(listOf(66), true).single() shouldBe 'B'
Petscii.decodePetscii(listOf(125u), false).single() shouldBe '│'
Petscii.decodePetscii(listOf(125u), true).single() shouldBe '│'
Petscii.decodePetscii(listOf(221u), false).single() shouldBe '│'
Petscii.decodePetscii(listOf(221u), true).single() shouldBe '│'
Petscii.decodeScreencode(listOf(93u), false).single() shouldBe '│'
Petscii.decodeScreencode(listOf(93u), true).single() shouldBe '│'
Petscii.decodeScreencode(listOf(66u), false).single() shouldBe '\uf13c' // "BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)"
Petscii.decodeScreencode(listOf(66u), true).single() shouldBe 'B'
}
})
+208 -9
View File
@@ -3,6 +3,7 @@ package prog8tests
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.instanceOf
import io.kotest.matchers.types.shouldBeSameInstanceAs
import prog8.ast.GlobalNamespace
@@ -10,13 +11,15 @@ import prog8.ast.base.ParentSentinel
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.*
import prog8.compiler.target.C64Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
class TestScoping: FunSpec({
test("testModulesParentIsGlobalNamespace") {
test("modules parent is global namespace") {
val src = """
main {
sub start() {
@@ -31,7 +34,7 @@ class TestScoping: FunSpec({
module.parent.parent shouldBe instanceOf<ParentSentinel>()
}
test("testAnonScopeVarsMovedIntoSubroutineScope") {
test("anon scope vars moved into subroutine scope") {
val src = """
main {
sub start() {
@@ -69,7 +72,7 @@ class TestScoping: FunSpec({
repeatbody.statements[1] shouldBe instanceOf<PostIncrDecr>()
}
test("testLabelsWithAnonScopes") {
test("labels with anon scopes") {
val src = """
main {
sub start() {
@@ -82,11 +85,9 @@ class TestScoping: FunSpec({
addr = &labelinside
addr = &labeloutside
addr = &main.start.nested.nestedlabel
addr = &nested.nestedlabel
goto labeloutside
goto iflabel
goto main.start.nested.nestedlabel
goto nested.nestedlabel
}
iflabel:
}
@@ -96,11 +97,9 @@ class TestScoping: FunSpec({
addr = &labelinside
addr = &labeloutside
addr = &main.start.nested.nestedlabel
addr = &nested.nestedlabel
goto iflabel
goto labelinside
goto main.start.nested.nestedlabel
goto nested.nestedlabel
labelinside:
}
@@ -116,9 +115,7 @@ class TestScoping: FunSpec({
addr = &labelinside
addr = &labeloutside
addr = &main.start.nested.nestedlabel
addr = &nested.nestedlabel
goto main.start.nested.nestedlabel
goto nested.nestedlabel
}
}
"""
@@ -133,4 +130,206 @@ class TestScoping: FunSpec({
}
}
test("good subroutine call without qualified names") {
val text="""
main {
sub start() {
routine()
routine2()
sub routine2() {
}
}
sub routine() {
start()
}
}
"""
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
}
test("wrong subroutine call without qualified names") {
val text="""
main {
sub start() {
sub routine2() {
}
}
sub routine() {
routine2()
}
}
"""
val errors= ErrorReporterForTests()
compileText(C64Target, false, text, writeAssembly = false, errors = errors).assertFailure()
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "undefined symbol: routine2"
}
test("good subroutine calls with qualified names (from root)") {
val text="""
main {
sub start() {
main.routine()
main.start.routine2()
sub routine2() {
}
}
sub routine() {
main.start.routine2()
}
}
"""
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
}
test("wrong subroutine calls with qualified names (not from root)") {
val text="""
main {
sub start() {
start.routine2()
wrong.start.routine2()
sub routine2() {
}
}
sub routine() {
start.routine2()
wrong.start.routine2()
}
}
"""
val errors= ErrorReporterForTests()
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
errors.errors.size shouldBe 4
errors.errors[0] shouldContain "undefined symbol: start.routine2"
errors.errors[1] shouldContain "undefined symbol: wrong.start.routine2"
errors.errors[2] shouldContain "undefined symbol: start.routine2"
errors.errors[3] shouldContain "undefined symbol: wrong.start.routine2"
}
test("good variables without qualified names") {
val text="""
main {
ubyte v1
sub start() {
ubyte v2
v1=1
v2=2
sub routine2() {
ubyte v3
v1=1
v2=2
v3=3
}
}
sub routine() {
ubyte v4
v1=1
v4=4
}
}
"""
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
}
test("wrong variables without qualified names") {
val text="""
main {
ubyte v1
sub start() {
ubyte v2
v1=1
v2=2
v3=3 ; can't access
v4=4 ; can't access
sub routine2() {
ubyte v3
v1=1
v2=2
v3=3
v4=3 ;can't access
}
}
sub routine() {
ubyte v4
v1=1
v2=2 ; can't access
v3=3 ; can't access
v4=4
}
}
"""
val errors= ErrorReporterForTests()
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
errors.errors.size shouldBe 5
errors.errors[0] shouldContain "undefined symbol: v3"
errors.errors[1] shouldContain "undefined symbol: v4"
errors.errors[2] shouldContain "undefined symbol: v4"
errors.errors[3] shouldContain "undefined symbol: v2"
errors.errors[4] shouldContain "undefined symbol: v3"
}
test("good variable refs with qualified names (from root)") {
val text="""
main {
sub start() {
uword xx
xx = &main.routine
main.routine(5)
main.routine.value = 5
main.routine.arg = 5
xx = &main.routine.nested
main.routine.nested(5)
main.routine.nested.nestedvalue = 5
main.routine.nested.arg2 = 5
}
sub routine(ubyte arg) {
ubyte value
sub nested(ubyte arg2) {
ubyte nestedvalue
}
}
}
"""
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
}
test("wrong variable refs with qualified names 1 (not from root)") {
val text="""
main {
sub start() {
uword xx
xx = &routine
routine(5)
routine.value = 5
routine.arg = 5
routine.nested.arg2 = 5
routine.nested.nestedvalue = 5
nested.nestedvalue = 5
}
sub routine(ubyte arg) {
ubyte value
sub nested(ubyte arg2) {
ubyte nestedvalue
}
}
}
"""
val errors= ErrorReporterForTests()
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
errors.errors.size shouldBe 5
errors.errors[0] shouldContain "undefined symbol: routine.value"
errors.errors[1] shouldContain "undefined symbol: routine.arg"
errors.errors[2] shouldContain "undefined symbol: routine.nested.arg2"
errors.errors[3] shouldContain "undefined symbol: routine.nested.nestedvalue"
errors.errors[4] shouldContain "undefined symbol: nested.nestedvalue"
}
})
+18 -17
View File
@@ -50,17 +50,18 @@ class TestSubroutines: FunSpec({
asmfunc.parameters.single().type shouldBe DataType.STR
asmfunc.statements.isEmpty() shouldBe true
func.isAsmSubroutine shouldBe false
func.parameters.single().type shouldBe DataType.STR
func.statements.size shouldBe 4
val paramvar = func.statements[0] as VarDecl
paramvar.name shouldBe "thing"
paramvar.datatype shouldBe DataType.STR
withClue("str param for normal subroutine should be changed into UWORD") {
func.parameters.single().type shouldBe DataType.UWORD
func.statements.size shouldBe 4
val paramvar = func.statements[0] as VarDecl
paramvar.name shouldBe "thing"
paramvar.datatype shouldBe DataType.UWORD
}
val assign = func.statements[2] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("t2")
withClue("str param in function body should not be transformed by normal compiler steps") {
assign.value shouldBe instanceOf<TypecastExpression>()
withClue("str param in function body should have been transformed into just uword assignment") {
assign.value shouldBe instanceOf<IdentifierReference>()
}
(assign.value as TypecastExpression).type shouldBe DataType.UWORD
val call = func.statements[3] as FunctionCallStatement
call.target.nameInSource.single() shouldBe "asmfunc"
withClue("str param in function body should not be transformed by normal compiler steps") {
@@ -127,7 +128,7 @@ class TestSubroutines: FunSpec({
(call.args.single() as IdentifierReference).nameInSource.single() shouldBe "thing"
}
test("arrayParameterNotYetAllowed_ButShouldPerhapsBe") {
test("array param not yet allowd (but should perhaps be?)") {
// note: the *parser* accepts this as it is valid *syntax*,
// however, it's not (yet) valid for the compiler
val text = """
@@ -187,14 +188,14 @@ class TestSubroutines: FunSpec({
func.statements.isEmpty() shouldBe true
}
test("testUwordParameterAndNormalVarIndexedAsArrayWorkAsDirectMemoryRead") {
test("uword param and normal varindexed as array work as DirectMemoryRead") {
val text="""
main {
sub thing(uword rr) {
ubyte xx = rr[1] ; should still work as var initializer that will be rewritten
ubyte yy
ubyte @shared xx = rr[1] ; should still work as var initializer that will be rewritten
ubyte @shared yy
yy = rr[2]
uword other
uword @shared other
ubyte zz = other[3]
}
@@ -210,14 +211,14 @@ class TestSubroutines: FunSpec({
val block = module.statements.single() as Block
val thing = block.statements.filterIsInstance<Subroutine>().single {it.name=="thing"}
block.name shouldBe "main"
thing.statements.size shouldBe 10 // rr, xx, xx assign, yy, yy assign, other, other assign 0, zz, zz assign, return
thing.statements.size shouldBe 11 // rr paramdecl, xx, xx assign, yy decl, yy init 0, yy assign, other, other assign 0, zz, zz assign, return
val xx = thing.statements[1] as VarDecl
withClue("vardecl init values must have been moved to separate assignments") {
xx.value shouldBe null
}
val assignXX = thing.statements[2] as Assignment
val assignYY = thing.statements[4] as Assignment
val assignZZ = thing.statements[8] as Assignment
val assignYY = thing.statements[5] as Assignment
val assignZZ = thing.statements[9] as Assignment
assignXX.target.identifier!!.nameInSource shouldBe listOf("xx")
assignYY.target.identifier!!.nameInSource shouldBe listOf("yy")
assignZZ.target.identifier!!.nameInSource shouldBe listOf("zz")
@@ -232,7 +233,7 @@ class TestSubroutines: FunSpec({
(valueZZexpr.right as NumericLiteralValue).number.toInt() shouldBe 3
}
test("testUwordParameterAndNormalVarIndexedAsArrayWorkAsMemoryWrite") {
test("uword param and normal varindexed as array work as MemoryWrite") {
val text="""
main {
sub thing(uword rr) {
+57 -45
View File
@@ -5,9 +5,12 @@ import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldBeIn
import io.kotest.matchers.collections.shouldNotBeIn
import io.kotest.matchers.ints.shouldBeGreaterThan
import io.kotest.matchers.comparables.shouldBeGreaterThan
import io.kotest.matchers.shouldBe
import prog8.ast.base.DataType
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
@@ -23,11 +26,20 @@ class TestAbstractZeropage: FunSpec({
override val machine: IMachineDefinition
get() = throw NotImplementedError("dummy")
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
throw NotImplementedError("dummy")
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
throw NotImplementedError("dummy")
}
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> {
throw NotImplementedError("dummy")
}
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>,
paramRegisters: List<RegisterOrStatusflag>): Boolean {
throw NotImplementedError("dummy")
}
@@ -38,13 +50,13 @@ class TestAbstractZeropage: FunSpec({
}
class DummyZeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1: Int = 0x10
override val SCRATCH_REG: Int = 0x11
override val SCRATCH_W1: Int= 0x20
override val SCRATCH_W2: Int = 0x30
override val SCRATCH_B1 = 0x10u
override val SCRATCH_REG = 0x11u
override val SCRATCH_W1 = 0x20u
override val SCRATCH_W2 = 0x30u
init {
free.addAll(0..255)
free.addAll(0u..255u)
removeReservedFromFreePool()
}
@@ -58,7 +70,7 @@ class TestAbstractZeropage: FunSpec({
OutputType.RAW,
LauncherType.NONE,
ZeropageType.FULL,
listOf((0x50..0x5f)),
listOf((0x50u..0x5fu)),
false,
false,
compTarget
@@ -156,22 +168,22 @@ class TestC64Zeropage: FunSpec({
test("testReservedSpace") {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
zp1.availableBytes() shouldBe 238
50 shouldBeIn zp1.free
100 shouldBeIn zp1.free
49 shouldBeIn zp1.free
101 shouldBeIn zp1.free
200 shouldBeIn zp1.free
255 shouldBeIn zp1.free
199 shouldBeIn zp1.free
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false, C64Target))
50u shouldBeIn zp1.free
100u shouldBeIn zp1.free
49u shouldBeIn zp1.free
101u shouldBeIn zp1.free
200u shouldBeIn zp1.free
255u shouldBeIn zp1.free
199u shouldBeIn zp1.free
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), false, false, C64Target))
zp2.availableBytes() shouldBe 139
50 shouldNotBeIn zp2.free
100 shouldNotBeIn zp2.free
49 shouldBeIn zp2.free
101 shouldBeIn zp2.free
200 shouldNotBeIn zp2.free
255 shouldNotBeIn zp2.free
199 shouldBeIn zp2.free
50u shouldNotBeIn zp2.free
100u shouldNotBeIn zp2.free
49u shouldBeIn zp2.free
101u shouldBeIn zp2.free
200u shouldNotBeIn zp2.free
255u shouldNotBeIn zp2.free
199u shouldBeIn zp2.free
}
test("testBasicsafeAllocation") {
@@ -187,7 +199,7 @@ class TestC64Zeropage: FunSpec({
for (i in 0 until zp.availableBytes()) {
val loc = zp.allocate("", DataType.UBYTE, null, errors)
loc shouldBeGreaterThan 0
loc shouldBeGreaterThan 0u
}
zp.availableBytes() shouldBe 0
zp.hasByteAvailable() shouldBe false
@@ -206,7 +218,7 @@ class TestC64Zeropage: FunSpec({
zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true
val loc = zp.allocate("", DataType.UWORD, null, errors)
loc shouldBeGreaterThan 3
loc shouldBeGreaterThan 3u
loc shouldNotBeIn zp.free
val num = zp.availableBytes() / 2
@@ -236,25 +248,25 @@ class TestC64Zeropage: FunSpec({
test("testEfficientAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
zp.availableBytes() shouldBe 18
zp.allocate("", DataType.WORD, null, errors) shouldBe 0x04
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x06
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0a
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9b
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9e
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xa5
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xb0
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xbe
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0e
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x92
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x96
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0xf9
zp.allocate("", DataType.WORD, null, errors) shouldBe 0x04u
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x06u
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0au
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9bu
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9eu
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xa5u
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xb0u
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xbeu
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0eu
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x92u
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x96u
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0xf9u
zp.availableBytes() shouldBe 0
}
test("testReservedLocations") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
withClue("zp _B1 and _REG must be next to each other to create a word") {
zp.SCRATCH_B1 + 1 shouldBe zp.SCRATCH_REG
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
}
}
})
@@ -266,7 +278,7 @@ class TestCx16Zeropage: FunSpec({
test("testReservedLocations") {
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, Cx16Target))
withClue("zp _B1 and _REG must be next to each other to create a word") {
zp.SCRATCH_B1 + 1 shouldBe zp.SCRATCH_REG
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
}
}
@@ -299,10 +311,10 @@ class TestCx16Zeropage: FunSpec({
test("testReservedSpace") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
zp1.availableBytes() shouldBe 216
0x22 shouldBeIn zp1.free
0x80 shouldBeIn zp1.free
0xff shouldBeIn zp1.free
0x02 shouldNotBeIn zp1.free
0x21 shouldNotBeIn zp1.free
0x22u shouldBeIn zp1.free
0x80u shouldBeIn zp1.free
0xffu shouldBeIn zp1.free
0x02u shouldNotBeIn zp1.free
0x21u shouldNotBeIn zp1.free
}
})
+3 -3
View File
@@ -22,15 +22,15 @@ internal val DummyFunctions = object : IBuiltinFunctions {
}
internal val DummyMemsizer = object : IMemSizer {
override fun memorySize(dt: DataType): Int = 0
override fun memorySize(dt: DataType) = 0
}
internal val DummyStringEncoder = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
return emptyList()
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
return ""
}
}
+4 -3
View File
@@ -4,6 +4,7 @@ import io.kotest.assertions.withClue
import io.kotest.matchers.shouldBe
import prog8.ast.Program
import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.c64.C64MachineDefinition
@@ -45,7 +46,7 @@ internal fun compileFile(
) : CompilationResult {
val filepath = fileDir.resolve(fileName)
assumeReadableFile(filepath)
return compileProgram(
val args = CompilerArguments(
filepath,
optimize,
optimizeFloatExpressions = optFloatExpr,
@@ -53,10 +54,10 @@ internal fun compileFile(
slowCodegenWarnings = false,
quietAssembler = true,
platform.name,
sourceDirs = listOf(),
outputDir,
outputDir = outputDir,
errors = errors ?: ErrorReporterForTests()
)
return compileProgram(args)
}
/**
@@ -219,7 +219,10 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
override fun visit(jump: Jump) {
output("goto ")
if(jump.isGosub)
output("gosub ")
else
output("goto ")
when {
jump.address!=null -> output(jump.address.toHex())
jump.generatedLabel!=null -> output(jump.generatedLabel)
@@ -262,7 +265,10 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
override fun visit(numLiteral: NumericLiteralValue) {
output(numLiteral.number.toString())
if(numLiteral.type==DataType.FLOAT)
output(numLiteral.number.toString())
else
output(numLiteral.number.toInt().toString())
}
override fun visit(char: CharLiteral) {
+25 -50
View File
@@ -68,6 +68,9 @@ interface IStatementContainer {
fun isNotEmpty(): Boolean = statements.isNotEmpty()
fun searchSymbol(name: String): Statement? {
if(this is Subroutine && isAsmSubroutine)
return searchAsmParameter(name)
// this is called quite a lot and could perhaps be optimized a bit more,
// but adding a memoization cache didn't make much of a practical runtime difference...
for (stmt in statements) {
@@ -75,16 +78,10 @@ interface IStatementContainer {
// is INamedStatement -> {
// if(stmt.name==name) return stmt
// }
is VarDecl -> {
if(stmt.name==name) return stmt
}
is Label -> {
if(stmt.name==name) return stmt
}
is Subroutine -> {
if(stmt.name==name)
return stmt
}
is VarDecl -> if(stmt.name==name) return stmt
is Label -> if(stmt.name==name) return stmt
is Subroutine -> if(stmt.name==name) return stmt
is Block -> if(stmt.name==name) return stmt
is AnonymousScope -> {
val found = stmt.searchSymbol(name)
if(found!=null)
@@ -154,41 +151,22 @@ interface INameScope: IStatementContainer, INamedStatement {
}
private fun lookupQualified(scopedName: List<String>): Statement? {
// a scoped name refers to a name in another namespace.
// look "up" from our current scope to search for the correct one.
val localScope = this.subScope(scopedName[0])
if(localScope!=null)
return resolveLocally(localScope, scopedName.drop(1))
var statementScope = this
while(statementScope !is GlobalNamespace) {
if(statementScope !is Module && statementScope.name==scopedName[0]) {
return resolveLocally(statementScope, scopedName.drop(1))
} else {
statementScope = (statementScope as Node).definingScope
}
}
// not found, try again but now assume it's a globally scoped name starting with the name of a block
// a scoped name refers to a name in another namespace, and stars from the root.
for(module in (this as Node).definingModule.program.modules) {
module.statements.forEach {
if(it is Block && it.name==scopedName[0])
return it.lookup(scopedName)
val block = module.searchSymbol(scopedName[0])
if(block!=null) {
var statement = block
for(name in scopedName.drop(1)) {
statement = (statement as? IStatementContainer)?.searchSymbol(name)
if(statement==null)
return null
}
return statement
}
}
return null
}
private fun resolveLocally(startScope: INameScope, name: List<String>): Statement? {
var scope: INameScope? = startScope
for(part in name.dropLast(1)) {
scope = scope!!.subScope(part)
if(scope==null)
return null
}
return scope!!.searchSymbol(name.last())
}
private fun lookupUnqualified(name: String): Statement? {
val builtinFunctionsNames = (this as Node).definingModule.program.builtinFunctions.names
if(name in builtinFunctionsNames) {
@@ -199,7 +177,6 @@ interface INameScope: IStatementContainer, INamedStatement {
}
// search for the unqualified name in the current scope (and possibly in any anonymousscopes it may contain)
// if it's not found there, jump up one higher in the namespaces and try again.
var statementScope = this
while(statementScope !is GlobalNamespace) {
val symbol = statementScope.searchSymbol(name)
@@ -262,6 +239,7 @@ interface Node {
}
fun replaceChildNode(node: Node, replacement: Node)
fun copy(): Node
}
@@ -277,8 +255,8 @@ open class Module(final override var statements: MutableList<Statement>,
.substringAfterLast("/")
.substringAfterLast("\\")
val loadAddress: Int by lazy {
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0
val loadAddress: UInt by lazy {
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0u
address
}
@@ -288,11 +266,6 @@ open class Module(final override var statements: MutableList<Statement>,
statements.forEach {it.linkParents(this)}
}
fun linkIntoProgram(program: Program) {
this.program = program
linkParents(program.namespace)
}
override val definingScope: INameScope
get() = program.namespace
override fun replaceChildNode(node: Node, replacement: Node) {
@@ -302,6 +275,8 @@ open class Module(final override var statements: MutableList<Statement>,
replacement.parent = this
}
override fun copy(): Node = throw NotImplementedError("no support for duplicating a Module")
override fun toString() = "Module(name=$name, pos=$position, lib=${isLibrary})"
fun accept(visitor: IAstVisitor) = visitor.visit(this)
@@ -317,6 +292,8 @@ class GlobalNamespace(val modules: Iterable<Module>): Node, INameScope {
override val statements = mutableListOf<Statement>() // not used
override var parent: Node = ParentSentinel
override fun copy(): Node = throw NotImplementedError("no support for duplicating a GlobalNamespace")
override fun lookup(scopedName: List<String>): Statement? {
throw NotImplementedError("use lookup on actual ast node instead")
}
@@ -330,12 +307,10 @@ class GlobalNamespace(val modules: Iterable<Module>): Node, INameScope {
}
}
object BuiltinFunctionScopePlaceholder : INameScope {
internal object BuiltinFunctionScopePlaceholder : INameScope {
override val name = "<<builtin-functions-scope-placeholder>>"
override val position = Position("<<placeholder>>", 0, 0, 0)
override var statements = mutableListOf<Statement>()
override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {}
}
+13 -1
View File
@@ -16,4 +16,16 @@ fun Number.toHex(): String {
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0')
else -> throw IllegalArgumentException("number too large for 16 bits $this")
}
}
}
fun UInt.toHex(): String {
// 0..15 -> "0".."15"
// 16..255 -> "$10".."$ff"
// 256..65536 -> "$0100".."$ffff"
return when (this) {
in 0u until 16u -> this.toString()
in 0u until 0x100u -> "$"+this.toString(16).padStart(2,'0')
in 0u until 0x10000u -> "$"+this.toString(16).padStart(4,'0')
else -> throw IllegalArgumentException("number too large for 16 bits $this")
}
}
+5 -4
View File
@@ -41,7 +41,8 @@ class Program(val name: String,
require(null == _modules.firstOrNull { it.name == module.name })
{ "module '${module.name}' already present" }
_modules.add(module)
module.linkIntoProgram(this)
module.program = this
module.linkParents(namespace)
return this
}
@@ -71,10 +72,10 @@ class Program(val name: String,
val toplevelModule: Module
get() = modules.first { it.name!= internedStringsModuleName }
val definedLoadAddress: Int
val definedLoadAddress: UInt
get() = toplevelModule.loadAddress
var actualLoadAddress: Int = 0
var actualLoadAddress = 0u
private val internedStringsUnique = mutableMapOf<Pair<String, Boolean>, List<String>>()
fun internString(string: StringLiteralValue): List<String> {
@@ -93,7 +94,7 @@ class Program(val name: String,
val varName = "string_${internedStringsBlock.statements.size}"
val decl = VarDecl(
VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, string,
isArray = false, autogeneratedDontRemove = true, sharedWithAsm = false, position = string.position
isArray = false, autogeneratedDontRemove = true, sharedWithAsm = false, subroutineParameter = null, position = string.position
)
internedStringsBlock.statements.add(decl)
decl.linkParents(internedStringsBlock)
+16 -12
View File
@@ -13,7 +13,7 @@ import kotlin.io.path.isRegularFile
/***************** Antlr Extension methods to create AST ****************/
private data class NumericLiteral(val number: Number, val datatype: DataType)
private data class NumericLiteral(val number: Double, val datatype: DataType)
private fun ParserRuleContext.toPosition() : Position {
@@ -43,7 +43,7 @@ internal fun Prog8ANTLRParser.BlockContext.toAst(isInLibrary: Boolean) : Block {
else -> throw FatalAstException("weird block node $it")
}
}
return Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
return Block(identifier().text, integerliteral()?.toAst()?.number?.toUInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
}
private fun Prog8ANTLRParser.Statement_blockContext.toAst(): MutableList<Statement> =
@@ -64,6 +64,7 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst() : Statement {
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
vd.SHARED()!=null,
null,
it.toPosition()
)
}
@@ -81,6 +82,7 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst() : Statement {
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
vd.SHARED() != null,
null,
cvarinit.toPosition()
)
}
@@ -98,6 +100,7 @@ private fun Prog8ANTLRParser.VariabledeclarationContext.toAst() : Statement {
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
vd.SHARED()!=null,
null,
mvarinit.toPosition()
)
}
@@ -193,7 +196,7 @@ private fun Prog8ANTLRParser.AsmsubroutineContext.toAst(): Subroutine {
private fun Prog8ANTLRParser.RomsubroutineContext.toAst(): Subroutine {
val subdecl = asmsub_decl().toAst()
val address = integerliteral().toAst().number.toInt()
val address = integerliteral().toAst().number.toUInt()
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, address, true, inline = false, statements = mutableListOf(), position = toPosition()
@@ -293,7 +296,7 @@ private fun Prog8ANTLRParser.ReturnstmtContext.toAst() : Return {
}
private fun Prog8ANTLRParser.UnconditionaljumpContext.toAst(): Jump {
val address = integerliteral()?.toAst()?.number?.toInt()
val address = integerliteral()?.toAst()?.number?.toUInt()
val identifier = scoped_identifier()?.toAst()
return Jump(address, identifier, null, toPosition())
}
@@ -354,7 +357,7 @@ private fun Prog8ANTLRParser.DirectiveargContext.toAst() : DirectiveArg {
if(str?.ALT_STRING_ENCODING() != null)
throw SyntaxError("can't use alternate string s for directive arguments", toPosition())
return DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition())
return DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toUInt(), toPosition())
}
private fun Prog8ANTLRParser.IntegerliteralContext.toAst(): NumericLiteral {
@@ -396,7 +399,7 @@ private fun Prog8ANTLRParser.IntegerliteralContext.toAst(): NumericLiteral {
}
else -> throw FatalAstException("invalid radix")
}
return NumericLiteral(integer, datatype)
return NumericLiteral(integer.toDouble(), datatype)
}
val terminal: TerminalNode = children[0] as TerminalNode
val integerPart = this.intpart.text
@@ -420,11 +423,11 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
val intLit = litval.integerliteral()?.toAst()
when {
intLit!=null -> when(intLit.datatype) {
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, intLit.number.toShort(), litval.toPosition())
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, intLit.number.toShort(), litval.toPosition())
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, intLit.number.toInt(), litval.toPosition())
DataType.WORD -> NumericLiteralValue(DataType.WORD, intLit.number.toInt(), litval.toPosition())
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, intLit.number.toDouble(), litval.toPosition())
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, intLit.number, litval.toPosition())
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, intLit.number, litval.toPosition())
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, intLit.number, litval.toPosition())
DataType.WORD -> NumericLiteralValue(DataType.WORD, intLit.number, litval.toPosition())
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, intLit.number, litval.toPosition())
else -> throw FatalAstException("invalid datatype for numeric literal")
}
litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
@@ -455,7 +458,7 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
if (rangefrom!=null && rangeto!=null) {
val defaultstep = if(rto.text == "to") 1 else -1
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, defaultstep, toPosition())
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, defaultstep.toDouble(), toPosition())
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
}
@@ -600,6 +603,7 @@ private fun Prog8ANTLRParser.VardeclContext.toAst(): VarDecl {
ARRAYSIG() != null || arrayindex() != null,
false,
SHARED()!=null,
null,
toPosition()
)
}
+3 -1
View File
@@ -29,7 +29,7 @@ enum class DataType {
UWORD -> targetType.oneOf(UWORD, FLOAT)
WORD -> targetType.oneOf(WORD, FLOAT)
FLOAT -> targetType == FLOAT
STR -> targetType == STR
STR -> targetType.oneOf(STR, UWORD)
in ArrayDatatypes -> targetType == this
else -> false
}
@@ -184,6 +184,8 @@ object ParentSentinel : Node {
override fun replaceChildNode(node: Node, replacement: Node) {
replacement.parent = this
}
override fun copy(): Node = throw FatalAstException("should never duplicate a ParentSentinel")
}
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
@@ -6,21 +6,23 @@ import prog8.ast.base.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import prog8.compilerinterface.IMemSizer
import java.util.*
import kotlin.math.round
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
val comparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val augmentAssignmentOperators = setOf("+", "-", "/", "*", "**", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
val logicalOperators = setOf("and", "or", "xor", "not")
val bitwiseOperators = setOf("&", "|", "^")
sealed class Expression: Node {
abstract override fun copy(): Expression
abstract fun constValue(program: Program): NumericLiteralValue?
abstract fun accept(visitor: IAstVisitor)
abstract fun accept(visitor: AstWalker, parent: Node)
abstract fun referencesIdentifier(vararg scopedName: String): Boolean
abstract fun referencesIdentifier(nameInSource: List<String>): Boolean
abstract fun inferType(program: Program): InferredTypes.InferredType
abstract val isSimple: Boolean
@@ -91,11 +93,33 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
replacement.parent = this
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun copy() = PrefixExpression(operator, expression.copy(), position)
override fun constValue(program: Program): NumericLiteralValue? {
val constval = expression.constValue(program) ?: return null
val converted = when(operator) {
"+" -> constval
"-" -> when (constval.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(-constval.number.toInt(), constval.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, -constval.number, constval.position)
else -> throw ExpressionError("can only take negative of int or float", constval.position)
}
"~" -> when (constval.type) {
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, constval.number.toInt().inv().toDouble(), constval.position)
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, (constval.number.toInt().inv() and 255).toDouble(), constval.position)
DataType.WORD -> NumericLiteralValue(DataType.WORD, constval.number.toInt().inv().toDouble(), constval.position)
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, (constval.number.toInt().inv() and 65535).toDouble(), constval.position)
else -> throw ExpressionError("can only take bitwise inversion of int", constval.position)
}
"not" -> NumericLiteralValue.fromBoolean(constval.number == 0.0, constval.position)
else -> throw FatalAstException("invalid operator")
}
converted.linkParents(this.parent)
return converted
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
override fun referencesIdentifier(nameInSource: List<String>) = expression.referencesIdentifier(nameInSource)
override fun inferType(program: Program): InferredTypes.InferredType {
val inferred = expression.inferType(program)
return when(operator) {
@@ -144,9 +168,8 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
replacement.parent = this
}
override fun toString(): String {
return "[$left $operator $right]"
}
override fun copy() = BinaryExpression(left.copy(), operator, right.copy(), position)
override fun toString() = "[$left $operator $right]"
override val isSimple = false
@@ -156,7 +179,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = left.referencesIdentifier(*scopedName) || right.referencesIdentifier(*scopedName)
override fun referencesIdentifier(nameInSource: List<String>) = left.referencesIdentifier(nameInSource) || right.referencesIdentifier(nameInSource)
override fun inferType(program: Program): InferredTypes.InferredType {
val leftDt = left.inferType(program)
val rightDt = right.inferType(program)
@@ -273,13 +296,13 @@ class ArrayIndexedExpression(var arrayvar: IdentifierReference,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = arrayvar.referencesIdentifier(*scopedName)
override fun referencesIdentifier(nameInSource: List<String>) = arrayvar.referencesIdentifier(nameInSource)
override fun inferType(program: Program): InferredTypes.InferredType {
val target = arrayvar.targetStatement(program)
if (target is VarDecl) {
return when (target.datatype) {
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
DataType.STR, DataType.UWORD -> InferredTypes.knownFor(DataType.UBYTE)
in ArrayDatatypes -> InferredTypes.knownFor(ArrayToElementTypes.getValue(target.datatype))
else -> InferredTypes.unknown()
}
@@ -291,7 +314,7 @@ class ArrayIndexedExpression(var arrayvar: IdentifierReference,
return "ArrayIndexed(ident=$arrayvar, arraysize=$indexer; pos=$position)"
}
fun copy() = ArrayIndexedExpression(arrayvar.copy(), indexer.copy(), position)
override fun copy() = ArrayIndexedExpression(arrayvar.copy(), indexer.copy(), position)
}
class TypecastExpression(var expression: Expression, var type: DataType, val implicit: Boolean, override val position: Position) : Expression() {
@@ -310,16 +333,20 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
replacement.parent = this
}
override fun copy() = TypecastExpression(expression.copy(), type, implicit, position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
override fun referencesIdentifier(nameInSource: List<String>) = expression.referencesIdentifier(nameInSource)
override fun inferType(program: Program) = InferredTypes.knownFor(type)
override fun constValue(program: Program): NumericLiteralValue? {
val cv = expression.constValue(program) ?: return null
val cast = cv.cast(type)
return if(cast.isValid)
cast.valueOrZero()
return if(cast.isValid) {
val newval = cast.valueOrZero()
newval.linkParents(parent)
return newval
}
else
null
}
@@ -345,8 +372,9 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
replacement.parent = this
}
override fun copy() = AddressOf(identifier.copy(), position)
override fun constValue(program: Program): NumericLiteralValue? = null
override fun referencesIdentifier(vararg scopedName: String) = false
override fun referencesIdentifier(nameInSource: List<String>) = identifier.nameInSource==nameInSource
override fun inferType(program: Program) = InferredTypes.knownFor(DataType.UWORD)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@@ -368,10 +396,11 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
replacement.parent = this
}
override fun copy() = DirectMemoryRead(addressExpression.copy(), position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = false
override fun referencesIdentifier(nameInSource: List<String>) = addressExpression.referencesIdentifier(nameInSource)
override fun inferType(program: Program) = InferredTypes.knownFor(DataType.UBYTE)
override fun constValue(program: Program): NumericLiteralValue? = null
@@ -381,43 +410,63 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
}
class NumericLiteralValue(val type: DataType, // only numerical types allowed
val number: Number, // can be byte, word or float depending on the type
numbervalue: 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==DataType.FLOAT)
numbervalue
else {
val rounded = round(numbervalue)
if(rounded != numbervalue)
throw ExpressionError("refused silent rounding of float to avoid loss of precision", position)
rounded
}
}
override val isSimple = true
fun copy() = NumericLiteralValue(type, number, position)
override fun copy() = NumericLiteralValue(type, number, position)
companion object {
fun fromBoolean(bool: Boolean, position: Position) =
NumericLiteralValue(DataType.UBYTE, if (bool) 1 else 0, position)
NumericLiteralValue(DataType.UBYTE, if (bool) 1.0 else 0.0, position)
fun optimalNumeric(value: Number, position: Position): NumericLiteralValue {
return if(value is Double) {
NumericLiteralValue(DataType.FLOAT, value, position)
} else {
when (val intval = value.toInt()) {
in 0..255 -> NumericLiteralValue(DataType.UBYTE, intval, position)
in -128..127 -> NumericLiteralValue(DataType.BYTE, intval, position)
in 0..65535 -> NumericLiteralValue(DataType.UWORD, intval, position)
in -32768..32767 -> NumericLiteralValue(DataType.WORD, intval, position)
else -> NumericLiteralValue(DataType.FLOAT, intval.toDouble(), position)
val dvalue = value.toDouble()
when (value.toInt()) {
in 0..255 -> NumericLiteralValue(DataType.UBYTE, dvalue, position)
in -128..127 -> NumericLiteralValue(DataType.BYTE, dvalue, position)
in 0..65535 -> NumericLiteralValue(DataType.UWORD, dvalue, position)
in -32768..32767 -> NumericLiteralValue(DataType.WORD, dvalue, position)
else -> NumericLiteralValue(DataType.FLOAT, dvalue, position)
}
}
}
fun optimalInteger(value: Int, position: Position): NumericLiteralValue {
val dvalue = value.toDouble()
return when (value) {
in 0..255 -> NumericLiteralValue(DataType.UBYTE, value, position)
in -128..127 -> NumericLiteralValue(DataType.BYTE, value, position)
in 0..65535 -> NumericLiteralValue(DataType.UWORD, value, position)
in -32768..32767 -> NumericLiteralValue(DataType.WORD, value, position)
else -> throw FatalAstException("integer overflow: $value")
in 0..255 -> NumericLiteralValue(DataType.UBYTE, dvalue, position)
in -128..127 -> NumericLiteralValue(DataType.BYTE, dvalue, position)
in 0..65535 -> NumericLiteralValue(DataType.UWORD, dvalue, position)
in -32768..32767 -> NumericLiteralValue(DataType.WORD, dvalue, position)
else -> throw FatalAstException("integer overflow: $dvalue")
}
}
fun optimalInteger(value: UInt, position: Position): NumericLiteralValue {
return when (value) {
in 0u..255u -> NumericLiteralValue(DataType.UBYTE, value.toDouble(), position)
in 0u..65535u -> NumericLiteralValue(DataType.UWORD, value.toDouble(), position)
else -> throw FatalAstException("unsigned integer overflow: $value")
}
}
}
val asBooleanValue: Boolean = number.toDouble() != 0.0
val asBooleanValue: Boolean = number != 0.0
override fun linkParents(parent: Node) {
this.parent = parent
@@ -427,7 +476,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
throw FatalAstException("can't replace here")
}
override fun referencesIdentifier(vararg scopedName: String) = false
override fun referencesIdentifier(nameInSource: List<String>) = false
override fun constValue(program: Program) = this
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
@@ -442,13 +491,13 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
override fun equals(other: Any?): Boolean {
if(other==null || other !is NumericLiteralValue)
return false
return number.toDouble()==other.number.toDouble()
return number==other.number
}
operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble())
operator fun compareTo(other: NumericLiteralValue): Int = number.compareTo(other.number)
class CastValue(val isValid: Boolean, private val value: NumericLiteralValue?) {
fun valueOrZero() = if(isValid) value!! else NumericLiteralValue(DataType.UBYTE, 0, Position.DUMMY)
fun valueOrZero() = if(isValid) value!! else NumericLiteralValue(DataType.UBYTE, 0.0, Position.DUMMY)
fun linkParent(parent: Node) {
value?.linkParents(parent)
}
@@ -463,55 +512,54 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
private fun internalCast(targettype: DataType): CastValue {
if(type==targettype)
return CastValue(true, this)
val numval = number.toDouble()
when(type) {
DataType.UBYTE -> {
if(targettype== DataType.BYTE && numval <= 127)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.BYTE && number <= 127)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.WORD || targettype== DataType.UWORD)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.FLOAT)
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
return CastValue(true, NumericLiteralValue(targettype, number, position))
}
DataType.BYTE -> {
if(targettype== DataType.UBYTE && numval >= 0)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.UWORD && numval >= 0)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if(targettype== DataType.UBYTE && number >= 0)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.UWORD && number >= 0)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.WORD)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.FLOAT)
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
return CastValue(true, NumericLiteralValue(targettype, number, position))
}
DataType.UWORD -> {
if(targettype== DataType.BYTE && numval <= 127)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.UBYTE && numval <= 255)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.WORD && numval <= 32767)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if(targettype== DataType.BYTE && number <= 127)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.UBYTE && number <= 255)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.WORD && number <= 32767)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.FLOAT)
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
return CastValue(true, NumericLiteralValue(targettype, number, position))
}
DataType.WORD -> {
if(targettype== DataType.BYTE && numval >= -128 && numval <=127)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.UBYTE && numval >= 0 && numval <= 255)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.UWORD && numval >=0)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if(targettype== DataType.BYTE && number >= -128 && number <=127)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.UBYTE && number >= 0 && number <= 255)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.UWORD && number >=0)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if(targettype== DataType.FLOAT)
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
return CastValue(true, NumericLiteralValue(targettype, number, position))
}
DataType.FLOAT -> {
if (targettype == DataType.BYTE && numval >= -128 && numval <=127)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if (targettype == DataType.UBYTE && numval >=0 && numval <= 255)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if (targettype == DataType.WORD && numval >= -32768 && numval <= 32767)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if (targettype == DataType.UWORD && numval >=0 && numval <= 65535)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if (targettype == DataType.BYTE && number >= -128 && number <=127)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if (targettype == DataType.UBYTE && number >=0 && number <= 255)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if (targettype == DataType.WORD && number >= -32768 && number <= 32767)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if (targettype == DataType.UWORD && number >=0 && number <= 65535)
return CastValue(true, NumericLiteralValue(targettype, number, position))
}
else -> {}
}
@@ -534,10 +582,11 @@ class CharLiteral(val value: Char,
throw FatalAstException("can't replace here")
}
override fun referencesIdentifier(vararg scopedName: String) = false
override fun copy() = CharLiteral(value, altEncoding, position)
override fun referencesIdentifier(nameInSource: List<String>) = false
override fun constValue(program: Program): NumericLiteralValue {
val bytevalue = program.encoding.encodeString(value.toString(), altEncoding).single()
return NumericLiteralValue(DataType.UBYTE, bytevalue, position)
return NumericLiteralValue(DataType.UBYTE, bytevalue.toDouble(), position)
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@@ -563,13 +612,13 @@ class StringLiteralValue(val value: String,
}
override val isSimple = true
fun copy() = StringLiteralValue(value, altEncoding, position)
override fun copy() = StringLiteralValue(value, altEncoding, position)
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
override fun referencesIdentifier(vararg scopedName: String) = false
override fun referencesIdentifier(nameInSource: List<String>) = false
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@@ -595,6 +644,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
value.forEach {it.linkParents(this)}
}
override fun copy() = throw NotImplementedError("no support for duplicating a ArrayLiteralValue")
override val isSimple = true
override fun replaceChildNode(node: Node, replacement: Node) {
@@ -604,7 +654,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
replacement.parent = this
}
override fun referencesIdentifier(vararg scopedName: String) = value.any { it.referencesIdentifier(*scopedName) }
override fun referencesIdentifier(nameInSource: List<String>) = value.any { it.referencesIdentifier(nameInSource) }
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@@ -620,14 +670,6 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
return type==other.type && value.contentEquals(other.value)
}
fun memsize(memsizer: IMemSizer): Int {
if(type.isKnown) {
val eltType = ArrayToElementTypes.getValue(type.getOr(DataType.UNDEFINED))
return memsizer.memorySize(eltType) * value.size
}
else throw IllegalArgumentException("array datatype is not yet known")
}
fun guessDatatype(program: Program): InferredTypes.InferredType {
// Educated guess of the desired array literal's datatype.
// If it's inside a for loop, assume the data type of the loop variable is what we want.
@@ -714,11 +756,12 @@ class RangeExpr(var from: Expression,
replacement.parent = this
}
override fun copy() = RangeExpr(from.copy(), to.copy(), step.copy(), position)
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String): Boolean = from.referencesIdentifier(*scopedName) || to.referencesIdentifier(*scopedName)
override fun referencesIdentifier(nameInSource: List<String>): Boolean = from.referencesIdentifier(nameInSource) || to.referencesIdentifier(nameInSource)
override fun inferType(program: Program): InferredTypes.InferredType {
val fromDt=from.inferType(program)
val toDt=to.inferType(program)
@@ -771,6 +814,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
throw FatalAstException("can't replace here")
}
override fun copy() = IdentifierReference(nameInSource, position)
override fun constValue(program: Program): NumericLiteralValue? {
val node = definingScope.lookup(nameInSource) ?: throw UndefinedSymbolError(this)
val vardecl = node as? VarDecl
@@ -789,8 +833,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String): Boolean =
nameInSource.size==scopedName.size && nameInSource.toTypedArray().contentEquals(scopedName)
override fun referencesIdentifier(nameInSource: List<String>): Boolean = this.nameInSource==nameInSource
override fun inferType(program: Program): InferredTypes.InferredType {
return when (val targetStmt = targetStatement(program)) {
@@ -821,6 +864,7 @@ class FunctionCall(override var target: IdentifierReference,
args.forEach { it.linkParents(this) }
}
override fun copy() = FunctionCall(target.copy(), args.map { it.copy() }.toMutableList(), position)
override val isSimple = target.nameInSource.size==1 && (target.nameInSource[0] in arrayOf("msb", "lsb", "peek", "peekw"))
override fun replaceChildNode(node: Node, replacement: Node) {
@@ -858,7 +902,7 @@ class FunctionCall(override var target: IdentifierReference,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String): Boolean = target.referencesIdentifier(*scopedName) || args.any{it.referencesIdentifier(*scopedName)}
override fun referencesIdentifier(nameInSource: List<String>): Boolean = target.referencesIdentifier(nameInSource) || args.any{it.referencesIdentifier(nameInSource)}
override fun inferType(program: Program): InferredTypes.InferredType {
val constVal = constValue(program ,false)
@@ -9,30 +9,26 @@ import prog8.ast.walk.IAstVisitor
interface INamedStatement {
val name: String
val scopedName: List<String>
get() {
val scopedName = mutableListOf(name)
var node: Node = this as Node
while (node !is Block) {
node = node.parent
if(node is INameScope) {
scopedName.add(0, node.name)
}
}
return scopedName
}
}
sealed class Statement : Node {
abstract override fun copy(): Statement
abstract fun accept(visitor: IAstVisitor)
abstract fun accept(visitor: AstWalker, parent: Node)
fun makeScopedName(name: String): String {
// easy way out is to always return the full scoped name.
// it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now.
// and like this, we can cache the name even,
// like in a lazy property on the statement object itself (label, subroutine, vardecl)
val scope = mutableListOf<String>()
var statementScope = this.parent
while(statementScope !is ParentSentinel && statementScope !is Module) {
if(statementScope is INameScope) {
scope.add(0, statementScope.name)
}
statementScope = statementScope.parent
}
if(name.isNotEmpty())
scope.add(name)
return scope.joinToString(".")
}
fun nextSibling(): Statement? {
val statements = (parent as? IStatementContainer)?.statements ?: return null
val nextIdx = statements.indexOfFirst { it===this } + 1
@@ -62,17 +58,21 @@ class BuiltinFunctionStatementPlaceholder(val name: String, override val positio
override fun replaceChildNode(node: Node, replacement: Node) {
replacement.parent = this
}
override fun copy() = throw NotImplementedError("no support for duplicating a BuiltinFunctionStatementPlaceholder")
}
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?)
class Block(override val name: String,
val address: Int?,
val address: UInt?,
override var statements: MutableList<Statement>,
val isInLibrary: Boolean,
override val position: Position) : Statement(), INameScope {
override lateinit var parent: Node
override fun copy() = throw NotImplementedError("no support for duplicating a Block")
override fun linkParents(parent: Node) {
this.parent = parent
statements.forEach {it.linkParents(this)}
@@ -87,10 +87,7 @@ class Block(override val name: String,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Block(name=$name, address=$address, ${statements.size} statements)"
}
override fun toString() = "Block(name=$name, address=$address, ${statements.size} statements)"
fun options() = statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.map {it.name!!}.toSet()
}
@@ -104,17 +101,19 @@ data class Directive(val directive: String, val args: List<DirectiveArg>, overri
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun copy() = Directive(directive, args.map { it.copy() }, position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
data class DirectiveArg(val str: String?, val name: String?, val int: Int?, override val position: Position) : Node {
data class DirectiveArg(val str: String?, val name: String?, val int: UInt?, override val position: Position) : Node {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun copy() = DirectiveArg(str, name, int, position)
}
data class Label(override val name: String, override val position: Position) : Statement(), INamedStatement {
@@ -125,12 +124,10 @@ data class Label(override val name: String, override val position: Position) : S
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun copy() = Label(name, position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Label(name=$name, pos=$position)"
}
override fun toString()= "Label(name=$name, pos=$position)"
}
open class Return(var value: Expression?, final override val position: Position) : Statement() {
@@ -147,12 +144,10 @@ open class Return(var value: Expression?, final override val position: Position)
replacement.parent = this
}
override fun copy() = Return(value?.copy(), position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Return($value, pos=$position)"
}
override fun toString() = "Return($value, pos=$position)"
}
class Break(override val position: Position) : Statement() {
@@ -163,6 +158,7 @@ class Break(override val position: Position) : Statement() {
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun copy() = Break(position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
@@ -184,6 +180,7 @@ open class VarDecl(val type: VarDeclType,
val isArray: Boolean,
val autogeneratedDontRemove: Boolean,
val sharedWithAsm: Boolean,
val subroutineParameter: SubroutineParameter?,
final override val position: Position) : Statement(), INamedStatement {
override lateinit var parent: Node
var allowInitializeWithZero = true
@@ -193,6 +190,16 @@ open class VarDecl(val type: VarDeclType,
companion object {
private var autoHeapValueSequenceNumber = 0
fun fromParameter(param: SubroutineParameter): VarDecl {
return VarDecl(VarDeclType.VAR, param.type, ZeropageWish.DONTCARE, null, param.name, null,
isArray = false,
autogeneratedDontRemove = true,
sharedWithAsm = false,
subroutineParameter = param,
position = param.position
)
}
fun createAuto(array: ArrayLiteralValue): VarDecl {
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
val arrayDt =
@@ -203,14 +210,14 @@ open class VarDecl(val type: VarDeclType,
val declaredType = ArrayToElementTypes.getValue(arrayDt)
val arraysize = ArrayIndex.forArray(array)
return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, array,
isArray = true, autogeneratedDontRemove = true, sharedWithAsm = false, position = array.position)
isArray = true, autogeneratedDontRemove = true, sharedWithAsm = false, subroutineParameter = null, position = array.position)
}
fun defaultZero(dt: DataType, position: Position) = when(dt) {
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0, position)
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0, position)
DataType.UWORD, DataType.STR -> NumericLiteralValue(DataType.UWORD, 0, position)
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position)
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0.0, position)
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0.0, position)
DataType.UWORD, DataType.STR -> NumericLiteralValue(DataType.UWORD, 0.0, position)
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0.0, position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
else -> throw FatalAstException("can only determine default zero value for a numeric type")
}
@@ -246,10 +253,8 @@ open class VarDecl(val type: VarDeclType,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, value=$value, pos=$position)"
}
override fun toString() =
"VarDecl(name=$name, vartype=$type, datatype=$datatype, value=$value, pos=$position)"
fun zeroElementValue(): NumericLiteralValue {
if(allowInitializeWithZero)
@@ -258,17 +263,14 @@ open class VarDecl(val type: VarDeclType,
throw IllegalArgumentException("attempt to get zero value for vardecl that shouldn't get it")
}
fun copy(): VarDecl {
val c = VarDecl(type, declaredDatatype, zeropage, arraysize, name, value, isArray, autogeneratedDontRemove, sharedWithAsm, position)
override fun copy(): VarDecl {
val c = VarDecl(type, declaredDatatype, zeropage, arraysize?.copy(), name, value?.copy(),
isArray, autogeneratedDontRemove, sharedWithAsm, subroutineParameter, position)
c.allowInitializeWithZero = this.allowInitializeWithZero
return c
}
}
// a vardecl used only for subroutine parameters
class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Position)
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, false, true, false, position)
class ArrayIndex(var indexExpr: Expression,
override val position: Position) : Node {
override lateinit var parent: Node
@@ -292,16 +294,13 @@ class ArrayIndex(var indexExpr: Expression,
}
fun accept(visitor: IAstVisitor) = indexExpr.accept(visitor)
fun accept(visitor: AstWalker, parent: Node) = indexExpr.accept(visitor, this)
override fun toString(): String {
return("ArrayIndex($indexExpr, pos=$position)")
}
fun accept(visitor: AstWalker) = indexExpr.accept(visitor, this)
override fun toString() = "ArrayIndex($indexExpr, pos=$position)"
fun constIndex() = (indexExpr as? NumericLiteralValue)?.number?.toInt()
infix fun isSameAs(other: ArrayIndex): Boolean = indexExpr isSameAs other.indexExpr
fun copy() = ArrayIndex(indexExpr, position)
override fun copy() = ArrayIndex(indexExpr.copy(), position)
}
open class Assignment(var target: AssignTarget, var value: Expression, final override val position: Position) : Statement() {
@@ -322,12 +321,10 @@ open class Assignment(var target: AssignTarget, var value: Expression, final ove
replacement.parent = this
}
override fun copy()= Assignment(target.copy(), value.copy(), position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return("Assignment(target: $target, value: $value, pos=$position)")
}
override fun toString() = "Assignment(target: $target, value: $value, pos=$position)"
/**
* Is the assigment value an expression that references the assignment target itself?
@@ -340,20 +337,35 @@ open class Assignment(var target: AssignTarget, var value: Expression, final ove
if(binExpr.left isSameAs target)
return true // A = A <operator> Something
if(binExpr.operator in "+-") {
val leftBinExpr = binExpr.left as? BinaryExpression
val rightBinExpr = binExpr.right as? BinaryExpression
if(rightBinExpr==null && leftBinExpr!=null && leftBinExpr.operator in "+-") {
// A = (A +- x) +- y
if(leftBinExpr.left isSameAs target || leftBinExpr.right isSameAs target || binExpr.right isSameAs target)
return true
}
if(leftBinExpr==null && rightBinExpr!=null && rightBinExpr.operator in "+-") {
// A = y +- (A +- x)
if(rightBinExpr.left isSameAs target || rightBinExpr.right isSameAs target || binExpr.left isSameAs target)
return true
}
}
if(binExpr.operator in associativeOperators) {
if (binExpr.left !is BinaryExpression && binExpr.right isSameAs target)
return true // A = v <associative-operator> A
val leftBinExpr = binExpr.left as? BinaryExpression
if(leftBinExpr?.operator == binExpr.operator) {
val rightBinExpr = binExpr.right as? BinaryExpression
if(leftBinExpr?.operator == binExpr.operator && rightBinExpr==null) {
// one of these?
// A = (A <associative-operator> x) <same-operator> y
// A = (x <associative-operator> A) <same-operator> y
// A = (x <associative-operator> y) <same-operator> A
return leftBinExpr.left isSameAs target || leftBinExpr.right isSameAs target || binExpr.right isSameAs target
}
val rightBinExpr = binExpr.right as? BinaryExpression
if(rightBinExpr?.operator == binExpr.operator) {
if(rightBinExpr?.operator == binExpr.operator && leftBinExpr==null) {
// one of these?
// A = y <associative-operator> (A <same-operator> x)
// A = y <associative-operator> (x <same-operator> y)
@@ -423,7 +435,7 @@ data class AssignTarget(var identifier: IdentifierReference?,
return when {
identifier != null -> identifier!!.copy()
arrayindexed != null -> arrayindexed!!.copy()
memoryAddress != null -> DirectMemoryRead(memoryAddress.addressExpression, memoryAddress.position)
memoryAddress != null -> DirectMemoryRead(memoryAddress.addressExpression.copy(), memoryAddress.position)
else -> throw FatalAstException("invalid assignmenttarget $this")
}
}
@@ -468,7 +480,7 @@ data class AssignTarget(var identifier: IdentifierReference?,
return false
}
fun copy() = AssignTarget(identifier?.copy(), arrayindexed?.copy(), memoryAddress?.copy(), position)
override fun copy() = AssignTarget(identifier?.copy(), arrayindexed?.copy(), memoryAddress?.copy(), position)
}
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
@@ -485,19 +497,18 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val
replacement.parent = this
}
override fun copy() = PostIncrDecr(target.copy(), operator, position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "PostIncrDecr(op: $operator, target: $target, pos=$position)"
}
override fun toString() = "PostIncrDecr(op: $operator, target: $target, pos=$position)"
}
class Jump(val address: Int?,
val identifier: IdentifierReference?,
val generatedLabel: String?, // used in code generation scenarios
override val position: Position) : Statement() {
open class Jump(val address: UInt?,
val identifier: IdentifierReference?,
val generatedLabel: String?, // can be used in code generation scenarios
override val position: Position) : Statement() {
override lateinit var parent: Node
open val isGosub = false
override fun linkParents(parent: Node) {
this.parent = parent
@@ -505,12 +516,22 @@ class Jump(val address: Int?,
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun copy() = Jump(address, identifier?.copy(), generatedLabel, position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
}
override fun toString() =
"Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
}
// a GoSub is ONLY created internally for calling subroutines
class GoSub(address: UInt?, identifier: IdentifierReference?, generatedLabel: String?, position: Position) :
Jump(address, identifier, generatedLabel, position) {
override val isGosub = true
override fun copy() = GoSub(address, identifier?.copy(), generatedLabel, position)
override fun toString() =
"GoSub(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
}
class FunctionCallStatement(override var target: IdentifierReference,
@@ -525,6 +546,8 @@ class FunctionCallStatement(override var target: IdentifierReference,
args.forEach { it.linkParents(this) }
}
override fun copy() = throw NotImplementedError("no support for duplicating a FunctionCallStatement")
override fun replaceChildNode(node: Node, replacement: Node) {
if(node===target)
target = replacement as IdentifierReference
@@ -537,10 +560,7 @@ class FunctionCallStatement(override var target: IdentifierReference,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "FunctionCallStatement(target=$target, pos=$position)"
}
override fun toString() = "FunctionCallStatement(target=$target, pos=$position)"
}
class InlineAssembly(val assembly: String, override val position: Position) : Statement() {
@@ -550,6 +570,9 @@ class InlineAssembly(val assembly: String, override val position: Position) : St
this.parent = parent
}
override fun copy() = throw NotImplementedError("no support for duplicating a InlineAssembly")
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@@ -571,6 +594,7 @@ class AnonymousScope(override var statements: MutableList<Statement>,
replacement.parent = this
}
override fun copy() = AnonymousScope(statements.map { it.copy() }.toMutableList(), position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
@@ -583,6 +607,7 @@ class NopStatement(override val position: Position): Statement() {
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun copy() = NopStatement(position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
@@ -598,7 +623,7 @@ class AsmGenInfo {
var usedFloatEvalResultVar1 = false
var usedFloatEvalResultVar2 = false
val extraVars = mutableListOf<Triple<DataType, String, Int?>>()
val extraVars = mutableListOf<Triple<DataType, String, UInt?>>()
}
// the subroutine class covers both the normal user-defined subroutines,
@@ -610,7 +635,7 @@ class Subroutine(override val name: String,
val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<CpuRegister>,
val asmAddress: Int?,
val asmAddress: UInt?,
val isAsmSubroutine: Boolean,
val inline: Boolean,
override var statements: MutableList<Statement>,
@@ -634,7 +659,8 @@ class Subroutine(override val name: String,
override lateinit var parent: Node
val asmGenInfo = AsmGenInfo()
val scopedname: String by lazy { makeScopedName(name) }
override fun copy() = throw NotImplementedError("no support for duplicating a Subroutine")
override fun linkParents(parent: Node) {
this.parent = parent
@@ -660,10 +686,8 @@ class Subroutine(override val name: String,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
}
override fun toString() =
"Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
fun regXasParam() = asmParameterRegisters.any { it.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
@@ -687,6 +711,24 @@ class Subroutine(override val name: String,
.filter { it is InlineAssembly }
.map { (it as InlineAssembly).assembly }
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it || " bra" in it || "\tbra" in it}
// code to provide the ability to reference asmsub parameters via qualified name:
private val asmParamsDecls = mutableMapOf<String, VarDecl>()
fun searchAsmParameter(name: String): VarDecl? {
require(isAsmSubroutine)
val existingDecl = asmParamsDecls[name]
if(existingDecl!=null)
return existingDecl
val param = parameters.firstOrNull {it.name==name} ?: return null
val decl = VarDecl.fromParameter(param)
decl.linkParents(this)
asmParamsDecls[name] = decl
return decl
}
}
open class SubroutineParameter(val name: String,
@@ -701,6 +743,9 @@ open class SubroutineParameter(val name: String,
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace anything in a subroutineparameter node")
}
override fun copy() = SubroutineParameter(name, type, position)
override fun toString() = "Param($type:$name)"
}
class IfStatement(var condition: Expression,
@@ -716,6 +761,8 @@ class IfStatement(var condition: Expression,
elsepart.linkParents(this)
}
override fun copy() = throw NotImplementedError("no support for duplicating a IfStatement")
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===condition -> condition = replacement as Expression
@@ -743,6 +790,8 @@ class BranchStatement(var condition: BranchCondition,
elsepart.linkParents(this)
}
override fun copy() = throw NotImplementedError("no support for duplicating a BranchStatement")
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===truepart -> truepart = replacement as AnonymousScope
@@ -770,6 +819,8 @@ class ForLoop(var loopVar: IdentifierReference,
body.linkParents(this)
}
override fun copy() = throw NotImplementedError("no support for duplicating a ForLoop")
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===loopVar -> loopVar = replacement as IdentifierReference
@@ -782,10 +833,7 @@ class ForLoop(var loopVar: IdentifierReference,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "ForLoop(loopVar: $loopVar, iterable: $iterable, pos=$position)"
}
override fun toString() = "ForLoop(loopVar: $loopVar, iterable: $iterable, pos=$position)"
fun loopVarDt(program: Program) = loopVar.inferType(program)
}
@@ -801,6 +849,8 @@ class WhileLoop(var condition: Expression,
body.linkParents(this)
}
override fun copy() = throw NotImplementedError("no support for duplicating a WhileLoop")
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===condition -> condition = replacement as Expression
@@ -823,6 +873,8 @@ class RepeatLoop(var iterations: Expression?, var body: AnonymousScope, override
body.linkParents(this)
}
override fun copy() = throw NotImplementedError("no support for duplicating a RepeatLoop")
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===iterations -> iterations = replacement as Expression
@@ -846,6 +898,7 @@ class UntilLoop(var body: AnonymousScope,
condition.linkParents(this)
body.linkParents(this)
}
override fun copy() = throw NotImplementedError("no support for duplicating a UntilLoop")
override fun replaceChildNode(node: Node, replacement: Node) {
when {
@@ -871,6 +924,8 @@ class WhenStatement(var condition: Expression,
choices.forEach { it.linkParents(this) }
}
override fun copy() = throw NotImplementedError("no support for duplicating a WhenStatement")
override fun replaceChildNode(node: Node, replacement: Node) {
if(node===condition)
condition = replacement as Expression
@@ -927,9 +982,8 @@ class WhenChoice(var values: MutableList<Expression>?, // if null, th
}
}
override fun toString(): String {
return "Choice($values at $position)"
}
override fun copy() = WhenChoice(values?.map{ it.copy() }?.toMutableList(), statements.copy(), position)
override fun toString() = "Choice($values at $position)"
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@@ -949,11 +1003,8 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
replacement.parent = this
}
override fun toString(): String {
return "DirectMemoryWrite($addressExpression)"
}
override fun toString() = "DirectMemoryWrite($addressExpression)"
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
fun copy() = DirectMemoryWrite(addressExpression, position)
override fun copy() = DirectMemoryWrite(addressExpression.copy(), position)
}
+2 -2
View File
@@ -235,7 +235,7 @@ abstract class AstWalker {
fun visit(decl: VarDecl, parent: Node) {
track(before(decl, parent), decl, parent)
decl.value?.accept(this, decl)
decl.arraysize?.accept(this, decl)
decl.arraysize?.accept(this)
track(after(decl, parent), decl, parent)
}
@@ -375,7 +375,7 @@ abstract class AstWalker {
fun visit(arrayIndexedExpression: ArrayIndexedExpression, parent: Node) {
track(before(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
arrayIndexedExpression.arrayvar.accept(this, arrayIndexedExpression)
arrayIndexedExpression.indexer.accept(this, arrayIndexedExpression)
arrayIndexedExpression.indexer.accept(this)
track(after(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
}
@@ -1,6 +1,6 @@
package prog8.compilerinterface
interface IStringEncoding {
fun encodeString(str: String, altEncoding: Boolean): List<Short>
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
fun encodeString(str: String, altEncoding: Boolean): List<UByte>
fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String
}
+1 -1
View File
@@ -132,7 +132,7 @@ sealed class SourceCode {
val inpStr = object {}.javaClass.getResourceAsStream(normalized)!!
// CharStreams.fromStream() doesn't allow us to set the stream name properly, so we use a lower level api
val channel = Channels.newChannel(inpStr)
return CharStreams.fromChannel(channel, StandardCharsets.UTF_8, 4096, CodingErrorAction.REPLACE, origin, -1);
return CharStreams.fromChannel(channel, StandardCharsets.UTF_8, 4096, CodingErrorAction.REPLACE, origin, -1)
}
override fun readText(): String {
+52 -5
View File
@@ -4,6 +4,7 @@ import io.kotest.assertions.fail
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.or
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
@@ -540,8 +541,8 @@ class TestProg8Parser: FunSpec( {
}
test("testLiteralValueComparisons") {
val ten = NumericLiteralValue(DataType.UWORD, 10, Position.DUMMY)
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position.DUMMY)
val ten = NumericLiteralValue(DataType.UWORD, 10.0, Position.DUMMY)
val nine = NumericLiteralValue(DataType.UBYTE, 9.0, Position.DUMMY)
ten shouldBe ten
nine shouldNotBe ten
(ten != ten) shouldBe false
@@ -666,7 +667,7 @@ class TestProg8Parser: FunSpec( {
""")
val module = parseModule(src)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
module.linkIntoProgram(program)
program.addModule(module)
val bb2 = (module.statements.single() as Block).statements[2] as VarDecl
val expr = bb2.value as BinaryExpression
println(expr)
@@ -689,7 +690,7 @@ class TestProg8Parser: FunSpec( {
""")
val module = parseModule(src)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
module.linkIntoProgram(program)
program.addModule(module)
val stmts = (module.statements.single() as Block).statements
stmts.size shouldBe 6
val qq = (stmts[2] as VarDecl).value as TypecastExpression
@@ -716,10 +717,56 @@ class TestProg8Parser: FunSpec( {
}""")
val module = parseModule(src)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
module.linkIntoProgram(program)
program.addModule(module)
val stmts = (module.statements.single() as Block).statements
stmts.size shouldBe 2
val ubexpr = (stmts[1] as VarDecl).value as TypecastExpression
ubexpr.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
}
test("assignment isAugmented correctness") {
val src = SourceCode.Text("""
main {
sub start() {
ubyte r
ubyte q
r = q*3 ; #1 no
r = r*3 ; #2 yes
r = 3*r ; #3 yes
r = 3*q ; #4 no
r = 5+r ; #5 yes
r = 5-r ; #6 no
r = r-5 ; #7 yes
r = not r ; #8 yes
r = not q ; #9 no
r = (q+r)+5 ; #10 yes
r = q+(r+5) ; #11 yes
r = (q+r)-5 ; #12 yes
r = q+(r-5) ; #13 yes
}
}""")
val module = parseModule(src)
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.addModule(module)
val stmts = program.entrypoint.statements
val expectedResults = listOf(
false, true, true,
false, true, false,
true, true, false,
true, true, true,
true
)
stmts.size shouldBe 15
expectedResults.size shouldBe stmts.size-2
for((idx, pp) in stmts.drop(2).zip(expectedResults).withIndex()) {
val assign = pp.first as Assignment
val expected = pp.second
withClue("#${idx+1}: should${if(expected) "" else "n't"} be augmentable: $assign") {
assign.isAugmentable shouldBe expected
assign.value shouldBe (instanceOf<PrefixExpression>() or instanceOf<BinaryExpression>() or instanceOf<TypecastExpression>())
}
}
}
})
+5 -5
View File
@@ -22,23 +22,23 @@ internal val DummyFunctions = object : IBuiltinFunctions {
}
internal val DummyMemsizer = object : IMemSizer {
override fun memorySize(dt: DataType): Int = 0
override fun memorySize(dt: DataType) = 0
}
internal val DummyStringEncoder = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
return emptyList()
}
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
return ""
}
}
internal val AsciiStringEncoder = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<Short> = str.map { it.code.toShort() }
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> = str.map { it.code.toUByte() }
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
return bytes.joinToString()
}
}
@@ -8,47 +8,45 @@ import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.AssignTarget
import kotlin.math.abs
fun AssignTarget.isInRegularRAMof(machine: IMachineDefinition): Boolean {
fun AssignTarget.isIOAddress(machine: IMachineDefinition): Boolean {
val memAddr = memoryAddress
val arrayIdx = arrayindexed
val ident = identifier
when {
memAddr != null -> {
val addr = memAddr.addressExpression.constValue(definingModule.program)
if(addr!=null)
return machine.isIOAddress(addr.number.toUInt())
return when (memAddr.addressExpression) {
is NumericLiteralValue -> {
machine.isRegularRAMaddress((memAddr.addressExpression as NumericLiteralValue).number.toInt())
}
is IdentifierReference -> {
val program = definingModule.program
val decl = (memAddr.addressExpression as IdentifierReference).targetVarDecl(program)
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
val decl = (memAddr.addressExpression as IdentifierReference).targetVarDecl(definingModule.program)
val result = if ((decl?.type == VarDeclType.MEMORY || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
machine.isIOAddress((decl.value as NumericLiteralValue).number.toUInt())
else
false
result
}
else -> false
}
}
arrayIdx != null -> {
val program = definingModule.program
val targetStmt = arrayIdx.arrayvar.targetVarDecl(program)
val targetStmt = arrayIdx.arrayvar.targetVarDecl(definingModule.program)
return if (targetStmt?.type == VarDeclType.MEMORY) {
val addr = targetStmt.value as? NumericLiteralValue
if (addr != null)
machine.isRegularRAMaddress(addr.number.toInt())
machine.isIOAddress(addr.number.toUInt())
else
false
} else true
} else false
}
ident != null -> {
val program = definingModule.program
val decl = ident.targetVarDecl(program) ?: throw FatalAstException("invalid identifier ${ident.nameInSource}")
val decl = ident.targetVarDecl(definingModule.program) ?: throw FatalAstException("invalid identifier ${ident.nameInSource}")
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
machine.isIOAddress((decl.value as NumericLiteralValue).number.toUInt())
else
true
false
}
else -> return true
else -> return false
}
}
@@ -71,11 +69,12 @@ fun RangeExpr.toConstantIntegerRange(): IntProgression? {
val fromLv = from as? NumericLiteralValue
val toLv = to as? NumericLiteralValue
if(fromLv==null || toLv==null)
val stepLv = step as? NumericLiteralValue
if(fromLv==null || toLv==null || stepLv==null)
return null
val fromVal = fromLv.number.toInt()
val toVal = toLv.number.toInt()
val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1
val stepVal = stepLv.number.toInt()
return makeRange(fromVal, toVal, stepVal)
}
@@ -126,22 +126,30 @@ private val functionSignatures: List<FSignature> = listOf(
FSignature("atan" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
FSignature("ln" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
FSignature("log2" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
FSignature("sqrt16" , true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
FSignature("sqrt16" , true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()) } },
FSignature("sqrt" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
FSignature("rad" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
FSignature("deg" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
FSignature("round" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
FSignature("round" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, ::round) },
FSignature("floor" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
FSignature("ceil" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
FSignature("lsb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 } },
FSignature("msb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255} },
FSignature("lsb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x and 255).toDouble() } },
FSignature("msb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x ushr 8 and 255).toDouble()} },
FSignature("mkword" , true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
FSignature("peek" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
FSignature("peekw" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
FSignature("poke" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
FSignature("pokew" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
FSignature("pop" , false, listOf(FParam("target", ByteDatatypes)), null),
FSignature("popw" , false, listOf(FParam("target", WordDatatypes)), null),
FSignature("push" , false, listOf(FParam("value", ByteDatatypes)), null),
FSignature("pushw" , false, listOf(FParam("value", WordDatatypes)), null),
FSignature("rsave" , false, emptyList(), null),
FSignature("rsavex" , false, emptyList(), null),
FSignature("rrestore" , false, emptyList(), null),
FSignature("rrestorex" , false, emptyList(), null),
FSignature("rnd" , false, emptyList(), DataType.UBYTE),
FSignature("rndw" , false, emptyList(), DataType.UWORD),
FSignature("rndf" , false, emptyList(), DataType.FLOAT),
@@ -155,15 +163,15 @@ private val functionSignatures: List<FSignature> = listOf(
val BuiltinFunctions = functionSignatures.associateBy { it.name }
private fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble() }!!
private fun builtinMax(array: List<Double>): Double = array.maxByOrNull { it }!!
private fun builtinMin(array: List<Number>): Number = array.minByOrNull { it.toDouble() }!!
private fun builtinMin(array: List<Double>): Double = array.minByOrNull { it }!!
private fun builtinSum(array: List<Number>): Number = array.sumOf { it.toDouble() }
private fun builtinSum(array: List<Double>): Double = array.sumOf { it }
private fun builtinAny(array: List<Number>): Number = if(array.any { it.toDouble()!=0.0 }) 1 else 0
private fun builtinAny(array: List<Double>): Double = if(array.any { it!=0.0 }) 1.0 else 0.0
private fun builtinAll(array: List<Number>): Number = if(array.all { it.toDouble()!=0.0 }) 1 else 0
private fun builtinAll(array: List<Double>): Double = if(array.all { it!=0.0 }) 1.0 else 0.0
fun builtinFunctionReturnType(function: String, args: List<Expression>, program: Program): InferredTypes.InferredType {
@@ -243,19 +251,18 @@ private fun oneDoubleArg(args: List<Expression>, position: Position, program: Pr
if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val float = constval.number.toDouble()
val float = constval.number
return numericLiteral(function(float), args[0].position)
}
private fun oneDoubleArgOutputWord(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
private fun oneDoubleArgOutputWord(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Double): NumericLiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val float = constval.number.toDouble()
return NumericLiteralValue(DataType.WORD, function(float).toInt(), args[0].position)
return NumericLiteralValue(DataType.WORD, function(constval.number), args[0].position)
}
private fun oneIntArgOutputInt(args: List<Expression>, position: Position, program: Program, function: (arg: Int)->Number): NumericLiteralValue {
private fun oneIntArgOutputInt(args: List<Expression>, position: Position, program: Program, function: (arg: Int)->Double): NumericLiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function requires one integer argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@@ -266,7 +273,7 @@ private fun oneIntArgOutputInt(args: List<Expression>, position: Position, progr
return numericLiteral(function(integer).toInt(), args[0].position)
}
private fun collectionArg(args: List<Expression>, position: Position, program: Program, function: (arg: List<Number>)->Number): NumericLiteralValue {
private fun collectionArg(args: List<Expression>, position: Position, program: Program, function: (arg: List<Double>)->Double): NumericLiteralValue {
if(args.size!=1)
throw SyntaxError("builtin function requires one non-scalar argument", position)
@@ -287,7 +294,7 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
return when (constval.type) {
in IntegerDatatypes -> numericLiteral(abs(constval.number.toInt()), args[0].position)
DataType.FLOAT -> numericLiteral(abs(constval.number.toDouble()), args[0].position)
DataType.FLOAT -> numericLiteral(abs(constval.number), args[0].position)
else -> throw SyntaxError("abs requires one numeric argument", position)
}
}
@@ -311,7 +318,7 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
numericLiteral(program.memsizer.memorySize(elementDt) * length, position)
}
dt istype DataType.STR -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
else -> NumericLiteralValue(DataType.UBYTE, program.memsizer.memorySize(dt.getOr(DataType.UNDEFINED)), position)
else -> NumericLiteralValue(DataType.UBYTE, program.memsizer.memorySize(dt.getOr(DataType.UNDEFINED)).toDouble(), position)
}
} else {
throw SyntaxError("sizeof invalid argument type", position)
@@ -359,7 +366,7 @@ private fun builtinMkword(args: List<Expression>, position: Position, program: P
val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
val constLsb = args[1].constValue(program) ?: throw NotConstArgumentException()
val result = (constMsb.number.toInt() shl 8) or constLsb.number.toInt()
return NumericLiteralValue(DataType.UWORD, result, position)
return NumericLiteralValue(DataType.UWORD, result.toDouble(), position)
}
@Suppress("UNUSED_PARAMETER")
@@ -367,8 +374,8 @@ private fun builtinSin8(args: List<Expression>, position: Position, program: Pro
if (args.size != 1)
throw SyntaxError("sin8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position)
val rad = constval.number /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.BYTE, round(127.0 * sin(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
@@ -376,8 +383,8 @@ private fun builtinSin8u(args: List<Expression>, position: Position, program: Pr
if (args.size != 1)
throw SyntaxError("sin8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position)
val rad = constval.number /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.UBYTE, round(128.0 + 127.5 * sin(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
@@ -385,8 +392,8 @@ private fun builtinSinR8(args: List<Expression>, position: Position, program: Pr
if (args.size != 1)
throw SyntaxError("sinr8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position)
val rad = constval.number / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.BYTE, round(127.0 * sin(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
@@ -394,8 +401,8 @@ private fun builtinSinR8u(args: List<Expression>, position: Position, program: P
if (args.size != 1)
throw SyntaxError("sinr8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position)
val rad = constval.number / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.UBYTE, round(128.0 + 127.5 * sin(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
@@ -403,8 +410,8 @@ private fun builtinCos8(args: List<Expression>, position: Position, program: Pro
if (args.size != 1)
throw SyntaxError("cos8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position)
val rad = constval.number /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.BYTE, round(127.0 * cos(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
@@ -412,8 +419,8 @@ private fun builtinCos8u(args: List<Expression>, position: Position, program: Pr
if (args.size != 1)
throw SyntaxError("cos8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position)
val rad = constval.number /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.UBYTE, round(128.0 + 127.5 * cos(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
@@ -421,8 +428,8 @@ private fun builtinCosR8(args: List<Expression>, position: Position, program: Pr
if (args.size != 1)
throw SyntaxError("cosr8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position)
val rad = constval.number / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.BYTE, round(127.0 * cos(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
@@ -430,8 +437,8 @@ private fun builtinCosR8u(args: List<Expression>, position: Position, program: P
if (args.size != 1)
throw SyntaxError("cosr8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position)
val rad = constval.number / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.UBYTE, round(128.0 + 127.5 * cos(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
@@ -439,8 +446,8 @@ private fun builtinSin16(args: List<Expression>, position: Position, program: Pr
if (args.size != 1)
throw SyntaxError("sin16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position)
val rad = constval.number /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.WORD, round(32767.0 * sin(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
@@ -448,8 +455,8 @@ private fun builtinSin16u(args: List<Expression>, position: Position, program: P
if (args.size != 1)
throw SyntaxError("sin16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position)
val rad = constval.number /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.UWORD, round(32768.0 + 32767.5 * sin(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
@@ -457,8 +464,8 @@ private fun builtinSinR16(args: List<Expression>, position: Position, program: P
if (args.size != 1)
throw SyntaxError("sinr16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position)
val rad = constval.number / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.WORD, round(32767.0 * sin(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
@@ -466,8 +473,8 @@ private fun builtinSinR16u(args: List<Expression>, position: Position, program:
if (args.size != 1)
throw SyntaxError("sinr16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position)
val rad = constval.number / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.UWORD, round(32768.0 + 32767.5 * sin(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
@@ -475,8 +482,8 @@ private fun builtinCos16(args: List<Expression>, position: Position, program: Pr
if (args.size != 1)
throw SyntaxError("cos16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position)
val rad = constval.number /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.WORD, round(32767.0 * cos(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
@@ -484,8 +491,8 @@ private fun builtinCos16u(args: List<Expression>, position: Position, program: P
if (args.size != 1)
throw SyntaxError("cos16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
val rad = constval.number /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.UWORD, round(32768.0 + 32767.5 * cos(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
@@ -493,8 +500,8 @@ private fun builtinCosR16(args: List<Expression>, position: Position, program: P
if (args.size != 1)
throw SyntaxError("cosr16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position)
val rad = constval.number / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.WORD, round(32767.0 * cos(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
@@ -502,8 +509,8 @@ private fun builtinCosR16u(args: List<Expression>, position: Position, program:
if (args.size != 1)
throw SyntaxError("cosr16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
val rad = constval.number / 180.0 * 2.0 * PI
return NumericLiteralValue(DataType.UWORD, round(32768.0 + 32767.5 * cos(rad)), position)
}
@Suppress("UNUSED_PARAMETER")
@@ -511,9 +518,11 @@ private fun builtinSgn(args: List<Expression>, position: Position, program: Prog
if (args.size != 1)
throw SyntaxError("sgn requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
return NumericLiteralValue(DataType.BYTE, constval.number.toDouble().sign.toInt().toShort(), position)
return NumericLiteralValue(DataType.BYTE, constval.number.sign, position)
}
private fun numericLiteral(value: UInt, position: Position): NumericLiteralValue = numericLiteral(value.toInt(), position)
private fun numericLiteral(value: Number, position: Position): NumericLiteralValue {
val floatNum=value.toDouble()
val tweakedValue: Number =
@@ -525,7 +534,7 @@ private fun numericLiteral(value: Number, position: Position): NumericLiteralVal
return when(tweakedValue) {
is Int -> NumericLiteralValue.optimalInteger(value.toInt(), position)
is Short -> NumericLiteralValue.optimalInteger(value.toInt(), position)
is Byte -> NumericLiteralValue(DataType.UBYTE, value.toShort(), position)
is Byte -> NumericLiteralValue(DataType.UBYTE, value.toDouble(), position)
is Double -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
is Float -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
else -> throw FatalAstException("invalid number type ${value::class}")
@@ -21,7 +21,7 @@ enum class ZeropageType {
class CompilationOptions(val output: OutputType,
val launcher: LauncherType,
val zeropage: ZeropageType,
val zpReserved: List<IntRange>,
val zpReserved: List<UIntRange>,
val floats: Boolean,
val noSysInit: Boolean,
val compTarget: ICompilationTarget,
@@ -1,8 +1,16 @@
package prog8.compilerinterface
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
interface ICompilationTarget: IStringEncoding, IMemSizer {
val name: String
val machine: IMachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short>
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
override fun encodeString(str: String, altEncoding: Boolean): List<UByte>
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String
fun asmsubArgsEvalOrder(sub: Subroutine): List<Int>
fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>,
paramRegisters: List<RegisterOrStatusflag>): Boolean
}
@@ -18,10 +18,10 @@ interface IMachineDefinition {
val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: Int
val POINTER_MEM_SIZE: Int
val ESTACK_LO: Int
val ESTACK_HI: Int
val BASIC_LOAD_ADDRESS : Int
val RAW_LOAD_ADDRESS : Int
val ESTACK_LO: UInt
val ESTACK_HI: UInt
val BASIC_LOAD_ADDRESS : UInt
val RAW_LOAD_ADDRESS : UInt
val opcodeNames: Set<String>
var zeropage: Zeropage
@@ -32,5 +32,5 @@ interface IMachineDefinition {
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
fun isRegularRAMaddress(address: Int): Boolean
fun isIOAddress(address: UInt): Boolean
}
@@ -8,14 +8,14 @@ class ZeropageDepletedError(message: String) : Exception(message)
abstract class Zeropage(protected val options: CompilationOptions) {
abstract val SCRATCH_B1 : Int // temp storage for a single byte
abstract val SCRATCH_REG : Int // temp storage for a register
abstract val SCRATCH_W1 : Int // temp storage 1 for a word $fb+$fc
abstract val SCRATCH_W2 : Int // temp storage 2 for a word $fb+$fc
abstract val SCRATCH_B1 : UInt // temp storage for a single byte
abstract val SCRATCH_REG : UInt // temp storage for a register
abstract val SCRATCH_W1 : UInt // temp storage 1 for a word $fb+$fc
abstract val SCRATCH_W2 : UInt // temp storage 2 for a word $fb+$fc
private val allocations = mutableMapOf<Int, Pair<String, DataType>>()
val free = mutableListOf<Int>() // subclasses must set this to the appropriate free locations.
private val allocations = mutableMapOf<UInt, Pair<String, DataType>>()
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
val allowedDatatypes = NumericDatatypes
@@ -23,7 +23,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
for (reserved in options.zpReserved)
reserve(reserved)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u))
}
fun availableBytes() = if(options.zeropage== ZeropageType.DONTUSE) 0 else free.size
@@ -32,9 +32,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
if(options.zeropage== ZeropageType.DONTUSE)
return 0
val words = free.windowed(2).filter { it[0] == it[1]-1 }
val words = free.windowed(2).filter { it[0] == it[1]-1u }
var nonOverlappingWordsCount = 0
var prevMsbLoc = -1
var prevMsbLoc = UInt.MAX_VALUE
for(w in words) {
if(w[0]!=prevMsbLoc) {
nonOverlappingWordsCount++
@@ -47,16 +47,16 @@ abstract class Zeropage(protected val options: CompilationOptions) {
if(options.zeropage== ZeropageType.DONTUSE)
return false
return free.windowed(2).any { it[0] == it[1] - 1 }
return free.windowed(2).any { it[0] == it[1] - 1u }
}
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): Int {
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): UInt {
require(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
if(options.zeropage== ZeropageType.DONTUSE)
throw InternalCompilerException("zero page usage has been disabled")
val size =
val size: Int =
when (datatype) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
@@ -66,7 +66,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
errors.warn("allocated a large value (float) in zeropage", position)
else
errors.warn("$scopedname: allocated a large value (float) in zeropage", position ?: Position.DUMMY)
5
options.compTarget.machine.FLOAT_MEM_SIZE
} else throw InternalCompilerException("floating point option not enabled")
}
else -> throw InternalCompilerException("cannot put datatype $datatype in zeropage")
@@ -74,13 +74,13 @@ abstract class Zeropage(protected val options: CompilationOptions) {
if(free.size > 0) {
if(size==1) {
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) {
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
if(loneByte(candidate))
return makeAllocation(candidate, 1, datatype, scopedname)
}
return makeAllocation(free[0], 1, datatype, scopedname)
}
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) {
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
if (sequentialFree(candidate, size))
return makeAllocation(candidate, size, datatype, scopedname)
}
@@ -89,14 +89,18 @@ abstract class Zeropage(protected val options: CompilationOptions) {
throw ZeropageDepletedError("ERROR: no free space in ZP to allocate $size sequential bytes")
}
protected fun reserve(range: IntRange) = free.removeAll(range)
protected fun reserve(range: UIntRange) = free.removeAll(range)
private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int {
free.removeAll(address until address+size)
private fun makeAllocation(address: UInt, size: Int, datatype: DataType, name: String?): UInt {
require(size>=0)
free.removeAll(address until address+size.toUInt())
allocations[address] = (name ?: "<unnamed>") to datatype
return address
}
private fun loneByte(address: Int) = address in free && address-1 !in free && address+1 !in free
private fun sequentialFree(address: Int, size: Int) = free.containsAll((address until address+size).toList())
private fun loneByte(address: UInt) = address in free && address-1u !in free && address+1u !in free
private fun sequentialFree(address: UInt, size: Int): Boolean {
require(size>0)
return free.containsAll((address until address+size.toUInt()).toList())
}
}
+65 -27
View File
@@ -63,48 +63,86 @@ Prog8 can create those, but it is also possible to output plain binary programs
that can be loaded into memory anywhere.
Compiling program code
----------------------
Running the compiler
--------------------
Make sure you have installed the :ref:`requirements`.
Compilation of program code is done by telling the Prog8 compiler to compile a main source code module file.
You run the Prog8 compiler on a main source code module file.
Other modules that this code needs will be loaded and processed via imports from within that file.
The compiler will link everything together into one output program at the end.
If you start the compiler without arguments, it will print a short usage text.
For normal use the compiler is invoked with the command:
For normal use the compiler can be invoked with the command:
``$ java -jar prog8compiler.jar sourcefile.p8``
``$ java -jar prog8compiler-7.3-all.jar sourcefile.p8``
Other options are also available, see the introduction page about how
to build and run the compiler.
(Use the appropriate name and version of the jar file downloaded from one of the Git releases.
Other ways to invoke the compiler are also available: see the introduction page about how
to build and run the compiler yourself)
By default, assembly code is generated and written to ``sourcefile.asm``.
It is then (automatically) fed to the `64tass <https://sourceforge.net/projects/tass64/>`_ cross assembler tool
that assembles it into the final program.
If you use the option to let the compiler auto-start an emulator, it will do so after
a successful compilation. This will load your program and the symbol and breakpoint lists
(for the machine code monitor) into the emulator.
It is then (automatically) fed to the `64tass <https://sourceforge.net/projects/tass64/>`_ assembler tool
that creastes the final runnable program.
Continuous compilation mode
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Almost instant compilation times (less than a second) can be achieved when using the continuous compilation mode.
Start the compiler with the ``-watch`` argument to enable this.
It will compile your program and then instead of exiting, it waits for any changes in the module source files.
As soon as a change happens, the program gets compiled again.
It is possible to use the watch mode with multiple modules as well, but it will
recompile everything in that list even if only one of the files got updated.
Other options
^^^^^^^^^^^^^
There's an option to specify the output directory if you're not happy with the default (the current working directory).
Also it is possible to specify more than one main module to compile:
this can be useful to quickly recompile multiple separate programs quickly.
(compiling in a batch like this is a lot faster than invoking the compiler again once per main file)
Command line options
^^^^^^^^^^^^^^^^^^^^
A short list and explanation of the options is printed if you launch the compiler with ``-h`` or ``-help``.
One or more .p8 module files
Specify the main module file(s) to compile.
Every file specified is a separate program.
``-help``, ``-h``
Prints short command line usage information.
``-target <compilation target>``
Sets the target output of the compiler, currently 'c64' and 'cx16' are valid targets.
c64 = Commodore-64, cx16 = Commander X16.
Default = c64
``-srcdirs <pathlist>``
Specify a list of extra paths (separated with ':'), to search in for imported modules.
Useful if you have library modules somewhere that you want to re-use,
or to switch implementations of certain routines via a command line switch.
``-emu``, ``-emu2``
Auto-starts target system emulator after successful compilation.
emu2 starts the alternative emulator if available.
The compiled program and the symbol and breakpoint lists
(for the machine code monitor) are immediately loaded into the emulator..
``-out <directory>``
sets directory location for output files instead of current directory
``-noasm``
Do not create assembly code and output program.
Useful for debugging or doing quick syntax checks.
``-noopt``
Don't perform any code optimizations.
Useful for debugging or faster compilation cycles.
``-optfloatx``
Also optimize float expressions if optimizations are enabled.
Warning: can increase program size significantly if a lot of floating point expressions are used.
``-watch``
Enables continuous compilation mode (watches for file changes).
This greatly increases compilation speed on subsequent runs:
almost instant compilation times (less than a second) can be achieved in this mode.
The compiler will compile your program and then instead of exiting, it waits for any changes in the module source files.
As soon as a change happens, the program gets compiled again.
Note that it is possible to use the watch mode with multiple modules as well, but it will
recompile everything in that list even if only one of the files got updated.
``-slowwarn``
Shows debug warnings about slow or problematic assembly code generation.
Ideally, the compiler should use as few stack based evaluations as possible.
``-quietasm``
Don't print assembler output results.
Module source code files
+1 -1
View File
@@ -176,7 +176,7 @@ using the ``-emu`` or ``-emu2`` command line options)
and roms, that are most accurately reflecting the current state of the hardware design of the CX16.
Unfortunately, v39 has not yet been officially released. At the time of writing, v38 is still
the latest official release. So, you have to either compile the v39 emulator+roms from the source in git yourself
or obtain a precompiled version from someone else.
or obtain a precompiled version from someone else. You can contact me if you require help doing this and use Linux.
A cx16 program compiled by prog8 7.0 is meant for v39 but *may* still work on the older v38 release of the emulator!
For this to work you should make sure that the program is not using floating point, nor the ram/rom bank switching logic provided by the libraries.
You can also choose to just stick with Prog8 6.4 (which still targets cx16 v38) and wait it out till
+28 -6
View File
@@ -119,18 +119,20 @@ It must be >= ``$0200`` (because ``$00``--``$ff`` is the ZP and ``$100``--``$1ff
.. _scopes:
**Scopes**
**Scoping rules**
.. sidebar::
Scoped access to symbols / "dotted names"
Use qualified names ("dotted names") to reference symbols defined elsewhere
Every symbol is 'public' and can be accessed from elsewhere given its full "dotted name".
In prog8 every symbol is 'public' and can be accessed from anywhere else, given its *full* "dotted name".
So, accessing a variable ``counter`` defined in subroutine ``worker`` in block ``main``,
can be done from anywhere by using ``main.worker.counter``.
*Symbols* are names defined in a certain *scope*. Inside the same scope, you can refer
to them by their 'short' name directly. If the symbol is not found in the same scope,
the enclosing scope is searched for it, and so on, until the symbol is found.
the enclosing scope is searched for it, and so on, up to the top level block, until the symbol is found.
If the symbol was not found the compiler will issue an error message.
Scopes are created using either of these two statements:
@@ -638,8 +640,8 @@ Defining a subroutine
Subroutines are parts of the code that can be repeatedly invoked using a subroutine call from elsewhere.
Their definition, using the ``sub`` statement, includes the specification of the required parameters and return value.
Subroutines can be defined in a Block, but also nested inside another subroutine. Everything is scoped accordingly.
With ``asmsub`` you can define a low-level subroutine that is implemented in inline assembly and takes any parameters
in registers directly.
With ``asmsub`` you can define a low-level subroutine that is implemented directly in assembly and takes parameters
directly in registers.
Trivial ``asmsub`` routines can be tagged as ``inline`` to tell the compiler to copy their code
in-place to the locations where the subroutine is called, rather than inserting an actual call and return to the
@@ -859,6 +861,20 @@ poke(address, value)
pokew(address, value)
writes the word value at the given address in memory, in usual little-endian lsb/msb byte order.
push(value)
pushes a byte value on the CPU hardware stack. Lowlevel function that should normally not be used.
pushw(value)
pushes a 16-bit word value on the CPU hardware stack. Lowlevel function that should normally not be used.
pop(variable)
pops a byte value off the CPU hardware stack into the given variable. Only variables can be used.
Lowlevel function that should normally not be used.
popw(value)
pops a 16-bit word value off the CPU hardware stack into the given variable. Only variables can be used.
Lowlevel function that should normally not be used.
rnd()
returns a pseudo-random byte from 0..255
@@ -935,6 +951,12 @@ callrom(bank, address, argumentaddress) ; NOTE: specific to cx16 compiler t
and you'll have to set up a call in assembly code yourself that handles the banking and
argument/returnvalues.
rsave, rsavex
Saves all registers including status (or only X) on the stack
rrestore, rrestorex
Restore all registers including status (or only X) back from the cpu hardware stack
Library routines
----------------
+5 -5
View File
@@ -194,12 +194,12 @@ Directives
.. data:: %asm {{ ... }}
Level: not at module scope.
Declares that there is *inline assembly code* in the lines enclosed by the curly braces.
This code will be written as-is into the generated output file.
The assembler syntax used should be for the 3rd party cross assembler tool that Prog8 uses.
Declares that a piece of *assembly code* is inside the curly braces.
This code will be copied as-is into the generated output assembly source file.
The assembler syntax used should be for the 3rd party cross assembler tool that Prog8 uses (64tass).
Note that the start and end markers are both *double curly braces* to minimize the chance
that the inline assembly itself contains either of those. If it does contain a ``}}``,
the parsing of the inline assembler block will end prematurely and cause compilation errors.
that the assembly code itself contains either of those. If it does contain a ``}}``,
it will confuse the parser.
Identifiers
+1 -1
View File
@@ -148,7 +148,7 @@ For the C64 these routines are::
c64.set_rasterirq(uword handler_address, uword rasterline, boolean useKernal)
c64.restore_irq() ; set everything back to the systems default irq handler
And for the Commander X16:
And for the Commander X16::
cx16.set_irq(uword handler_address, boolean useKernal) ; vsync irq
cx16.set_rasterirq(uword handler_address, uword rasterline) ; note: disables kernal irq handler! sys.wait() won't work anymore
+2 -2
View File
@@ -115,5 +115,5 @@ inaccessible unless you write a short piece of inline assembly code to deal with
Prog8 also provides some help to deal with this:
- you should use a ``clobbers(X)`` specification for asmsub routines that modify the X register; the compiler will preserve it for you automatically when such a routine is called
- the ``sys.rsave()`` and ``sys.rrestore()`` routines can preserve and restore *all* registers (but this is very slow and overkill if you only need to save X)
- the ``rsavex()`` and ``rrestorex()`` builtin functions can preserve and restore the X register
- the ``rsave()`` and ``rrestore()`` builtin functions can preserve and restore *all* registers (but this is very slow and overkill if you only need to save X)
+7 -9
View File
@@ -1,7 +1,7 @@
TODO
====
For next compiler release (7.3)
For next compiler release (7.5)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
@@ -14,14 +14,9 @@ Blocked by an official Commander-x16 v39 release
Future
^^^^^^
- rethink the whole "isAugmentable" business. Because the way this is determined, should always also be exactly mirrorred in the AugmentableAssignmentAsmGen or you'll get a crash at code gen time.
- simplifyConditionalExpression() should not split expression if it still results in stack-based evaluation
- remove special code generation for while and util expression
by rewriting while and until expressions into if+jump (consider them syntactic sugar)
but the result should not produce larger code ofcourse!
- while-expression should now also get the simplifyConditionalExpression() treatment
- fix the asm-labels problem (github issue #62)
- find a way to optimize asm-subroutine param passing where it now sometimes uses the evalstack?
- document the various compiler command line options in more detail. See "Compiling program code" in the docs
- get rid of all TODO's in the code
- improve testability further, add more tests
- replace certain uses of inferredType.getOr(DataType.UNDEFINED) by i.getOrElse({ errorhandler })
@@ -39,11 +34,14 @@ Future
Perhaps replace all uses of .proc/.pend by .block/.bend will fix that?
(but we lose the optimizing aspect of the assembler where it strips out unused code.
There's not really a dynamic switch possible as all assembly lib code is static and uses one or the other)
- introduce byte-index operator to avoid index multiplications in loops over arrays? see github issue #4
More code optimization ideas
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- detect variables that are written but never read - mark those as unused too and remove them, such as ``uword unused = memory("unused222", 20)`` - also remove this memory slab allocation
- remove special code generation for while and util expression
by rewriting while and until expressions into if+jump (just consider them syntactic sugar)
but the result should not produce larger code ofcourse!
- while-expression should now also get the simplifyConditionalExpression() treatment
- rewrite expression tree evaluation such that it doesn't use an eval stack but flatten the tree into linear code that uses a fixed number of predetermined value 'variables'
- this removes the need for the BinExprSplitter (which is problematic now)
- introduce byte-index operator to avoid index multiplications in loops over arrays? see github issue #4
+6 -26
View File
@@ -1,6 +1,4 @@
%import textio
%import test_stack
%zeropage basicsafe
; Note: this program is compatible with C64 and CX16.
@@ -31,12 +29,8 @@ main {
DY[lp] = rnd() & 1
}
; start clock
c64.SETTIM(0,0,0)
; display balls
uword frame
for frame in 0 to 999 {
repeat {
; Loop though all balls clearing current spot and setting new spot
for lp in 0 to ballCount-1 {
@@ -45,14 +39,11 @@ main {
if DX[lp] == 0 {
if (BX[lp] == 0)
{
DX[lp] = 1
} else {
else
BX[lp]=BX[lp]-1
}
} else if DX[lp] == 1 {
if (BX[lp] == txt.DEFAULT_WIDTH-1)
{
if (BX[lp] == txt.DEFAULT_WIDTH-1) {
BX[lp] = txt.DEFAULT_WIDTH-2
DX[lp] = 0
} else {
@@ -61,14 +52,11 @@ main {
}
if DY[lp] == 0 {
if (BY[lp] == 0)
{
DY[lp] = 1
} else {
else
BY[lp]=BY[lp]-1
}
} else if DY[lp] == 1 {
if (BY[lp] == txt.DEFAULT_HEIGHT-1)
{
if (BY[lp] == txt.DEFAULT_HEIGHT-1) {
BY[lp] = txt.DEFAULT_HEIGHT-2
DY[lp] = 0
} else {
@@ -80,15 +68,7 @@ main {
txt.setclr(BX[lp], BY[lp], BC[lp])
}
;txt.plot(0,0)
;txt.print_uw(frame)
sys.waitvsync()
}
uword jiffies = c64.RDTIM16()
txt.print("\nbenchmark: ")
txt.print_uw(jiffies)
txt.print(" jiffies for 1000 frames.\n")
; test_stack.test()
}
}
+2
View File
@@ -25,6 +25,8 @@ main {
anglex+=500
angley+=215
anglez+=453
sys.waitvsync()
sys.waitvsync()
}
}
+5 -6
View File
@@ -77,16 +77,15 @@ main {
for y in 24 downto 0 {
for x in 39 downto 0 {
; using a temp var here to enable expression optimization that can't be done on a 'problematic' ROM/RAM memory location
ubyte cc = xbuf[x] + ybuf[y]
ubyte @zp cc = xbuf[x] + ybuf[y]
@(screen+x) = cc
; this is the fastest way to do this inner part:
; %asm {{
; ldy i
; lda xbuf,y
; ldy ii
; ldy y
; lda ybuf,y
; ldy x
; clc
; adc ybuf,y
; ldy #0
; adc xbuf,y
; sta (screen),y
; }}
}
+28 -72
View File
@@ -1,80 +1,36 @@
%import textio
%import test_stack
%zeropage dontuse
main {
sub start() {
ubyte xx=10
txt.print("if\n")
if xx-2 {
txt.print("in if\n")
}
txt.print("\nwhile\n")
xx=6
while xx-2 {
xx--
txt.print_ub(xx)
txt.spc()
}
txt.print("\nuntil\n")
xx=2
do {
xx++
txt.print_ub(xx)
txt.spc()
} until xx+2==10
txt.print("\ndone")
repeat {
}
ubyte @shared dummy
word b1 = 1111
byte b2 = 22
word b3 = 3333
dummy++
func(-b1,-b2,-b3)
}
; if not iteration_in_progress or not num_bytes
; return
; word xx=0
; word[] xarr = [1,2,3]
; ubyte ai
;
; if not @($c000) {
; txt.print("xx is zero\n")
; }
;
; while not xarr[ai] {
; xx ++
; }
;
; do {
; xx--
; } until not xarr[ai]
;
; if not xarr[ai] {
; txt.print("xx is zero\n")
; }
; ubyte yy=$30
; ubyte zz=9
; sys.memset(xx+200, yy*2, ~yy)
;
;
; if yy & %10000 {
; yy++
; }
;
; @($c030) = 10
; @(~xx) *= 2
; txt.print_ub(@($c030))
;
; float f1 = 1111.11
; float f2 = 2222.22
; float[] fa = [2222.22, 3333.33]
;
; swap(f1, fa[1])
; floats.print_f(f1)
; txt.nl()
; floats.print_f(fa[1])
sub printz(word a1, byte a2, word a3) {
txt.print_w(a1)
txt.spc()
txt.print_b(a2)
txt.spc()
txt.print_w(a3)
txt.nl()
}
asmsub func(word a1 @XY, byte a2 @A, word a3 @R0) {
%asm {{
stx printz.a1
sty printz.a1+1
sta printz.a2
lda cx16.r0
sta printz.a3
lda cx16.r0+1
sta printz.a3+1
jmp printz
}}
}
}
+6 -4
View File
@@ -43,16 +43,18 @@ main {
irq {
ubyte angle
ubyte angle1=200
ubyte angle2=0
sub irqhandler() {
angle++
angle1 += 2
angle2 += 3
c64.MSIGX=0
ubyte @zp spri
for spri in 7 downto 0 {
c64.EXTCOL++
uword @zp x = sin8u(angle*2-spri*16) as uword + 50
ubyte @zp y = cos8u(angle*3-spri*16) / 2 + 70
uword @zp x = sin8u(angle1-spri*16) as uword + 50
ubyte @zp y = sin8u(angle2-spri*16) / 2 + 70
c64.SPXYW[spri] = mkword(y, lsb(x))
c64.MSIGX <<= 1
if msb(x) c64.MSIGX++
+1 -1
View File
@@ -4,4 +4,4 @@ org.gradle.parallel=true
org.gradle.daemon=true
kotlin.code.style=official
javaVersion=11
kotlinVersion=1.5.31
kotlinVersion=1.6.0
@@ -11,6 +11,7 @@ import org.takes.facets.fork.TkFork
import org.takes.rq.form.RqFormBase
import org.takes.rs.RsJson
import org.takes.tk.TkSlf4j
import prog8.compiler.CompilerArguments
import javax.json.Json
import prog8.compiler.compileProgram
import java.nio.file.Path
@@ -29,16 +30,16 @@ class RequestParser : Take {
val form = RqFormBase(request)
val names = form.names()
val a = form.param("a").single()
val compilationResult = compileProgram(Path.of(a),
val args = CompilerArguments(
Path.of(a),
optimize = true,
optimizeFloatExpressions = false,
writeAssembly = true,
slowCodegenWarnings = true,
compilationTarget = "c64",
sourceDirs = emptyList(),
outputDir = Path.of(""),
quietAssembler = false
)
val compilationResult = compileProgram(args)
return RsJson(Jsonding())
}
}
+2 -2
View File
@@ -14,10 +14,10 @@
<keywords keywords="&amp;;-&gt;;@;\$;and;as;asmsub;break;clobbers;do;downto;else;false;for;goto;if;if_cc;if_cs;if_eq;if_mi;if_ne;if_neg;if_nz;if_pl;if_pos;if_vc;if_vs;if_z;in;inline;not;or;repeat;return;romsub;step;sub;to;true;until;when;while;xor;~" ignore_case="false" />
<keywords2 keywords="%address;%asm;%asmbinary;%asminclude;%breakpoint;%import;%launcher;%option;%output;%zeropage;%zpreserved" />
<keywords3 keywords="byte;const;float;shared;str;ubyte;uword;void;word;zp" />
<keywords4 keywords="abs;acos;all;any;asin;atan;avg;callfar;callrom;ceil;cmp;cos;cos16;cos16u;cos8;cos8u;deg;floor;len;ln;log2;lsb;lsl;lsr;max;memory;min;mkword;msb;peek;peekw;poke;pokew;rad;reverse;rnd;rndf;rndw;rol;rol2;ror;ror2;round;sgn;sin;sin16;sin16u;sin8;sin8u;sizeof;sort;sqrt;sqrt16;sum;swap;tan" />
<keywords4 keywords="abs;acos;all;any;asin;atan;avg;callfar;callrom;ceil;cmp;cos;cos16;cos16u;cos8;cos8u;cosr16;cosr16u;cosr8;cosr8u;deg;floor;len;ln;log2;lsb;lsl;lsr;max;memory;min;mkword;msb;peek;peekw;poke;pokew;pop;popw;push;pushw;rad;reverse;rnd;rndf;rndw;rol;rol2;ror;ror2;round;rrestore;rrestorex;rsave;rsavex;sgn;sin;sin16;sin16u;sin8;sin8u;sinr16;sinr16u;sinr8;sinr8u;sizeof;sort;sqrt;sqrt16;sum;swap;tan" />
</highlighting>
<extensionMap>
<mapping ext="p8" />
<mapping ext="prog8" />
</extensionMap>
</filetype>
</filetype>
+1 -1
View File
@@ -27,7 +27,7 @@
<Keywords name="Keywords1">void const&#x000D;&#x000A;str&#x000D;&#x000A;byte ubyte&#x000D;&#x000A;word uword&#x000D;&#x000A;float&#x000D;&#x000A;zp shared</Keywords>
<Keywords name="Keywords2">%address&#x000D;&#x000A;%asm&#x000D;&#x000A;%asmbinary&#x000D;&#x000A;%asminclude&#x000D;&#x000A;%breakpoint&#x000D;&#x000A;%import&#x000D;&#x000A;%launcher&#x000D;&#x000A;%option&#x000D;&#x000A;%output&#x000D;&#x000A;%zeropage&#x000D;&#x000A;%zpreserved</Keywords>
<Keywords name="Keywords3">inline sub asmsub romsub&#x000D;&#x000A;clobbers&#x000D;&#x000A;asm&#x000D;&#x000A;if&#x000D;&#x000A;when else&#x000D;&#x000A;if_cc if_cs if_eq if_mi if_neg if_nz if_pl if_pos if_vc if_vs if_z&#x000D;&#x000A;for in step do while repeat&#x000D;&#x000A;break return goto</Keywords>
<Keywords name="Keywords4">abs acos all any asin atan avg callfar callrom ceil cmp cos cos16 cos16u cos8 cos8u deg floor len ln log2 lsb lsl lsr max memory min mkword msb peek peekw poke pokew rad reverse rnd rndf rndw rol rol2 ror ror2 round sgn sin sin16 sin16u sin8 sin8u sizeof sort sqrt sqrt16 sum swap tan&#x000D;&#x000A;</Keywords>
<Keywords name="Keywords4">abs acos all any asin atan avg callfar callrom ceil cmp cos cos16 cos16u cos8 cos8u cosr8 cosr8u cosr16 cosr16u deg floor len ln log2 lsb lsl lsr max memory min mkword msb peek peekw poke pokew push pushw pop popw rsave rsavex rrestore rrestorex rad reverse rnd rndf rndw rol rol2 ror ror2 round sgn sin sin16 sin16u sin8 sin8u sinr8 sinr8u sinr16 sinr16u sizeof sort sqrt sqrt16 sum swap tan&#x000D;&#x000A;</Keywords>
<Keywords name="Keywords5">true false&#x000D;&#x000A;not and or xor&#x000D;&#x000A;as to downto</Keywords>
<Keywords name="Keywords6"></Keywords>
<Keywords name="Keywords7"></Keywords>
+4 -4
View File
@@ -7,16 +7,16 @@
" Built-in functions
" Math functions
syn keyword prog8BuiltInFunc abs atan ceil cos cos8u cos8 cos16u cos16 deg floor
syn keyword prog8BuiltInFunc ln log2 rad round sin sgn sin8u sin8 sin16u sin16
syn keyword prog8BuiltInFunc abs atan ceil cos cos8u cos8 cos16u cos16 cosr8 cosr8u cosr16 cosr16u deg floor
syn keyword prog8BuiltInFunc ln log2 rad round sin sgn sin8u sin8 sin16u sin16 sinr8 sinr8u sinr16 sinr16u
syn keyword prog8BuiltInFunc sqrt16 sqrt tan
" Array functions
syn keyword prog8BuiltInFunc any all len max min reverse sum sort
" Miscellaneous functions
syn keyword prog8BuiltInFunc cmp lsb msb mkword peek peekw poke pokew rnd rndw
syn keyword prog8BuiltInFunc rndf rol rol2 ror ror2 sizeof
syn keyword prog8BuiltInFunc cmp lsb msb mkword peek peekw poke pokew rnd rndw push pushw pop popw rsave rsavex rrestore rrestorex
syn keyword prog8BuiltInFunc rndf rol rol2 ror ror2 sizeof
syn keyword prog8BuiltInFunc swap memory callfar callrom