Compare commits

..
74 Commits
Author SHA1 Message Date
Irmen de Jong c57ef7725e preparing v7.6 2022-01-04 20:40:35 +01:00
Irmen de Jong 3ae07503f2 doc css styling: font size slightly bigger for code as well 2022-01-03 23:28:11 +01:00
Irmen de Jong 9a0341adde doc css styling: font size slightly bigger 2022-01-03 23:16:07 +01:00
Irmen de Jong 96225efd96 library doc tweaks 2022-01-03 23:15:34 +01:00
Irmen de Jong c3bd904f41 todo 2022-01-02 23:46:36 +01:00
Irmen de Jong 74257163b1 fix that memory("name", ...) also allocates a STR variable for the name 2022-01-02 17:07:04 +01:00
Irmen de Jong 7bc75fd220 fix that memory("a b c", ...) produces invalid symbol 2022-01-02 16:11:53 +01:00
Irmen de Jong a23281afab added experimental -noreinit option 2022-01-01 16:35:36 +01:00
Irmen de Jong 9e90dbdde6 slight optimization of repeat loop (> 256 iters) code generation on 65c02 cpu 2022-01-01 14:42:37 +01:00
Irmen de Jong 1e8d8e40a2 slight optimization of repeat loop (0-256 iters) code generation on 65c02 cpu 2021-12-31 14:06:35 +01:00
Irmen de Jong 583e208c1e remark 2021-12-31 11:34:53 +01:00
Irmen de Jong 9b91c427a1 add porting guide
sizeof(pointer) is hardcoded as 2 now
2021-12-31 00:16:23 +01:00
Irmen de Jong 196c5e9c24 v39->r39 2021-12-30 19:05:56 +01:00
Irmen de Jong d8f7feb672 cleanup code style 2021-12-30 18:47:38 +01:00
Irmen de Jong 7c889f17b9 c128 fixes 2021-12-30 18:33:26 +01:00
Irmen de Jong c15a75556d Merge branch 'master' into c128target
# Conflicts:
#	compiler/src/prog8/CompilerMain.kt
#	examples/test.p8
2021-12-30 18:22:05 +01:00
Irmen de Jong 5275c2e35f todo porting guide 2021-12-30 18:20:09 +01:00
Irmen de Jong 5267e06969 added -asmlist cli option to produce assembler listing output 2021-12-30 14:42:09 +01:00
Irmen de Jong b62183adcb slightly optimized binexpr evaluation for ==/!= in some cases 2021-12-30 02:00:36 +01:00
Irmen de Jong 5d2dec1803 added missing codegen for augmented ==/!= 2021-12-30 01:34:10 +01:00
Irmen de Jong 4a98dab948 fix compiler warnings 2021-12-30 00:58:33 +01:00
Irmen de Jong 9f8c70b326 fix warning about testing multiple values 2021-12-30 00:49:36 +01:00
Irmen de Jong a65404e63a doc 2021-12-29 18:24:05 +01:00
Irmen de Jong 05a1ddad05 Merge branch 'master' into c128target
# Conflicts:
#	examples/test.p8
2021-12-29 18:14:24 +01:00
Irmen de Jong 4be3d63c0e slight optimization of if-in 2021-12-29 18:13:43 +01:00
Irmen de Jong de6ce4a46e add "X in [1,2,3]" expression (efficient containment check) 2021-12-29 17:26:00 +01:00
Irmen de Jong 7a9e5afb93 fix: for loop over array literal no longer crashes the compiler 2021-12-28 17:51:38 +01:00
Irmen de Jong b2876b0a03 add a suggestion to use when statement if it seems appropriate 2021-12-28 16:38:12 +01:00
Irmen de Jong b66f66fe6a fix renames 2021-12-28 14:32:27 +01:00
Irmen de Jong 30f04962d4 Merge branch 'master' into c128target
# Conflicts:
#	codeGeneration/src/prog8/codegen/target/C128Target.kt
#	codeGeneration/src/prog8/codegen/target/c128/C128MachineDefinition.kt
#	codeGeneration/src/prog8/codegen/target/c128/C128Zeropage.kt
#	compiler/src/prog8/CompilerMain.kt
#	compiler/src/prog8/compiler/Compiler.kt
2021-12-28 14:30:11 +01:00
Irmen de Jong 0feeb88024 codegen package rename 2 2021-12-28 14:23:36 +01:00
Irmen de Jong b799f2e05b codegen package rename 2021-12-28 14:18:04 +01:00
Irmen de Jong 56d21de001 Merge branch 'master' into c128target
# Conflicts:
#	examples/test.p8
2021-12-28 13:57:27 +01:00
Irmen de Jong 7b54aa0c7d more consistent naming of the statement classes 2021-12-28 13:56:47 +01:00
Irmen de Jong 6e11b8ada1 GoSub no longer inherits from Jump node, fixes subtle ast/codegen bugs related to jsrs 2021-12-28 01:55:13 +01:00
Irmen de Jong 98d25fc4e9 fix some unneeded open classes 2021-12-28 01:29:08 +01:00
Irmen de Jong 79405f47f6 fix if-gosub 2021-12-28 01:24:31 +01:00
Irmen de Jong 1c7c4fc3b0 optimized if-goto codegeneration 2021-12-28 00:42:00 +01:00
Irmen de Jong 97e84d0977 tweak if statement handling 2021-12-27 15:04:25 +01:00
Irmen de Jong 9906b58818 tweak while desugaring, moved postfixexpr optimizations to VariousCleanups regardless of optimizer setting because asmgen requires these for conditional expressions 2021-12-27 12:41:26 +01:00
Irmen de Jong 371f084884 comment 2021-12-27 02:17:04 +01:00
Irmen de Jong 1c10839c14 moved peek/poke desugaring to other walker 2021-12-27 02:08:47 +01:00
Irmen de Jong c55fdd9834 removed special code generation for while and util expression (replaced by jumps)
also added exhaustive parent node checker in validation step
2021-12-27 02:04:28 +01:00
Irmen de Jong 67b0890a6e remove unneeded var inits when an assignment is already present 2021-12-25 23:31:25 +01:00
Irmen de Jong 4da4f96669 lower code: break -> goto after (simplifies codegen) 2021-12-25 22:30:38 +01:00
Irmen de Jong 60a64c2558 test program for floating point issues on c128 2021-12-25 14:36:10 +01:00
Irmen de Jong d4153da8b9 setup float routine addresses for c128 2021-12-25 02:34:52 +01:00
Irmen de Jong fc33ab8905 shuffled some system functions back to c64 block to remain compatible with existing code, added missing float and graphics library stubs 2021-12-24 00:08:32 +01:00
Irmen de Jong a123c64f59 Merge branch 'master' into c128target 2021-12-23 23:51:42 +01:00
Irmen de Jong a090fe3834 no compiler optimizer crash on certain missing symbol 2021-12-23 23:51:21 +01:00
Irmen de Jong 8fa84de28e fix c128 clearscreen and bdmusic sound issue 2021-12-22 22:59:36 +01:00
Irmen de Jong 3e3da38de1 correctly disable charset switching 2021-12-22 21:47:41 +01:00
Irmen de Jong a3be8ccc87 Merge branch 'master' into c128target 2021-12-22 21:25:49 +01:00
Irmen de Jong cdfef30c22 fixed docs on rsave()/rrestore() builtin functions 2021-12-22 21:24:36 +01:00
Irmen de Jong cabf1e82e8 some shadow registers added to make uppercase()/lowercase() work 2021-12-22 21:20:34 +01:00
Irmen de Jong 608dc5e284 readme 2021-12-22 00:36:06 +01:00
Irmen de Jong 836d40072f c128 evalstack corrections 2021-12-22 00:07:05 +01:00
Irmen de Jong 431401d90e c128 corrections 2021-12-21 23:37:15 +01:00
Irmen de Jong 6da83e2bd7 first steps to add C128 compiler target 2021-12-21 19:08:33 +01:00
Irmen de Jong 7bccfc0006 proper position in recursion warning 2021-12-17 21:04:41 +01:00
Irmen de Jong e051e09c1d trim down number of warnings a bit 2021-12-17 20:21:14 +01:00
Irmen de Jong 1462c57d0c no need for intermediary returnvalue var for prefix expressions 2021-12-16 21:00:38 +01:00
Irmen de Jong 77c2b2b326 fix position of @shared in array var declarations so that the order is now type[] @shared 2021-12-16 20:36:05 +01:00
Irmen de Jong 3cf9b9d9a5 code size optimization: subroutine calls with 2 byte arg will pass it via A/Y registers instead of separate param assignments at every call site 2021-12-16 01:48:22 +01:00
Irmen de Jong 629117e594 code size optimization: subroutine calls with 1 int arg will pass it via register instead of separate param assignment at every call site 2021-12-16 00:56:59 +01:00
Irmen de Jong 08f87c321f fixed capitalization of operator sets to be consistent with other sets names 2021-12-15 23:43:14 +01:00
Irmen de Jong 1ff13723fe implicit int to float conversion is now an error if floats are not enabled. 2021-12-15 01:52:28 +01:00
Irmen de Jong 510bda1b28 fix compiler crash when using floats in a comparison expression 2021-12-15 01:24:25 +01:00
Irmen de Jong 890327b381 the returnvalue of the diskio.load() function family now is just the last load address+1 (like kernal's LOAD routine).
This fixes the inconsistent attempt to calculate a size, just let the caller do this if required.
Added a small helper function in cx16diskio to do this for loads that span multiple banks.
2021-12-14 23:54:42 +01:00
Irmen de Jong b21f7411dd fix compiler crash when trying to concatenate string var and string literal. 2021-12-14 23:07:46 +01:00
Irmen de Jong 5df623bd2e doc 2021-12-14 22:40:03 +01:00
Irmen de Jong 1e9d249f71 fixed output of float values in cmp instructions 2021-12-13 00:17:59 +01:00
Irmen de Jong a7b5949e6a fix compiler crash when using a gosub/subroutinecall in a branch statement 2021-12-11 15:11:16 +01:00
Irmen de Jong 02010170ce fix compiler crash when attempting to call a non-function 2021-12-11 13:20:13 +01:00
115 changed files with 5340 additions and 1175 deletions

No files matched your search

+6 -4
View File
@@ -37,7 +37,8 @@ What does Prog8 provide?
- small program boilerplate/compilersupport overhead
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
- conditional branches
- 'when' statement to provide a concise jump table alternative to if/elseif chains
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
- ``in`` expression for concise and efficient multi-value/containment check
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
- various powerful built-in libraries to do I/O, number conversions, graphics and more
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
@@ -52,11 +53,12 @@ What does Prog8 provide?
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
*Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!):
*Multiple supported compiler targets* (contributions to improve these or to add support for other machines are welcome!):
- "c64": Commodore-64 (6510 CPU = almost a 6502)
- "c64": Commodore-64 (6502 like CPU)
- "c128": Commodore-128 (6502 like CPU - the Z80 cpu mode is not supported)
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU)
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag)
+48
View File
@@ -0,0 +1,48 @@
; this program adds 2 floats 1.2345 + 4.4444 and prints the result 5.67879
; it works fine on C64 and CX16 but fails to add the right number on C128
; I think it has something to do with the banking on the C128
; because several rom fp routines mention "value in bank 1"
; but I don't know what this means and why the program as given doesn't work
.cpu '6502'
* = $1300 ; sys 4864
P8ZP_SCRATCH_W1 = 251 ; free zp word
CHROUT = $ffd2
; c128:
MOVFM = $af63
FADD = $af18
FOUT = $af06
;cx16:
;MOVFM = $fe42
;FADD = $fe12
;FOUT = $fe7b
;c64:
;MOVFM = $bba2
;FADD = $b867
;FOUT = $bddd
lda #<f1
ldy #>f1
jsr MOVFM ; load FAC1
lda #<f2
ldy #>f2
jsr FADD ; add mem float to FAC1 DOESN'T WORK ON C128!?
jsr FOUT ; convert to string
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
_printloop lda (P8ZP_SCRATCH_W1),y
beq _done
jsr CHROUT
iny
bne _printloop
_done jmp _done
f1 .byte $81, $1e, $04, $18, $93 ; float 1.2345
f2 .byte $83, $0e, $38, $86, $59 ; float 4.4444
@@ -1,3 +1,3 @@
package prog8.compiler.target
package prog8.codegen.target
class AssemblyError(msg: String) : RuntimeException(msg)
@@ -0,0 +1,41 @@
package prog8.codegen.target
import com.github.michaelbull.result.fold
import prog8.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.codegen.target.c128.C128MachineDefinition
import prog8.codegen.target.cbm.Petscii
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compilerinterface.ICompilationTarget
object C128Target: ICompilationTarget {
override val name = "c128"
override val machine = C128MachineDefinition()
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<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, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
else -> Int.MIN_VALUE
}
}
}
@@ -1,14 +1,14 @@
package prog8.compiler.target
package prog8.codegen.target
import com.github.michaelbull.result.fold
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.codegen.target.c64.C64MachineDefinition
import prog8.codegen.target.cbm.Petscii
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compilerinterface.ICompilationTarget
@@ -33,9 +33,8 @@ object C64Target: ICompilationTarget {
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> Int.MIN_VALUE
}
}
@@ -1,4 +1,4 @@
package prog8.compiler.target
package prog8.codegen.target
import com.github.michaelbull.result.fold
import prog8.ast.base.ByteDatatypes
@@ -8,10 +8,10 @@ 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.codegen.target.cbm.Petscii
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.codegen.target.cx16.CX16MachineDefinition
import prog8.compilerinterface.ICompilationTarget
@@ -36,9 +36,8 @@ object Cx16Target: ICompilationTarget {
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> Int.MIN_VALUE
}
}
@@ -0,0 +1,68 @@
package prog8.codegen.target.c128
import prog8.ast.base.DataType
import prog8.codegen.target.c64.normal6502instructions
import prog8.codegen.target.cbm.Mflpt5
import prog8.codegen.target.cbm.viceMonListName
import prog8.compilerinterface.*
import java.io.IOException
import java.nio.file.Path
class C128MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU6502
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val BASIC_LOAD_ADDRESS = 0x1c01u
override val RAW_LOAD_ADDRESS = 0x1300u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override val ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive
override val ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive
override lateinit var zeropage: Zeropage
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
if(selectedEmulator!=1) {
System.err.println("The c128 target only supports the main emulator (Vice).")
return
}
for(emulator in listOf("x128")) {
println("\nStarting C-128 emulator $emulator...")
val viceMonlist = viceMonListName(programNameWithPath.toString())
val cmdline = listOf(emulator, "-silent", "-moncommands", viceMonlist,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process
try {
process=processb.start()
} catch(x: IOException) {
continue // try the next emulator executable
}
process.waitFor()
break
}
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
override fun getPreallocatedZeropageVars(): Map<String, Pair<UInt, DataType>> = emptyMap()
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = C128Zeropage(compilerOptions)
}
override val opcodeNames = normal6502instructions
}
@@ -0,0 +1,42 @@
package prog8.codegen.target.c128
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.InternalCompilerException
import prog8.compilerinterface.Zeropage
import prog8.compilerinterface.ZeropageType
class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x9bu // temp storage for a single byte
override val SCRATCH_REG = 0x9cu // 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 $fd+$fe
init {
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,
ZeropageType.DONTUSE
))
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
when (options.zeropage) {
ZeropageType.FULL -> {
// TODO all c128 usable zero page locations, except the ones used by the system's IRQ routine
free.addAll(0x0au..0xffu) // TODO c128 what about $02-$09?
// TODO c128 free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
}
ZeropageType.KERNALSAFE,
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE -> {
free.clear() // TODO c128 usable zero page addresses
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
}
removeReservedFromFreePool()
}
}
@@ -1,8 +1,8 @@
package prog8.compiler.target.c64
package prog8.codegen.target.c64
import prog8.ast.base.DataType
import prog8.compiler.target.cbm.Mflpt5
import prog8.compiler.target.cbm.viceMonListPostfix
import prog8.codegen.target.cbm.Mflpt5
import prog8.codegen.target.cbm.viceMonListName
import prog8.compilerinterface.*
import java.io.IOException
import java.nio.file.Path
@@ -15,7 +15,6 @@ class C64MachineDefinition: IMachineDefinition {
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val POINTER_MEM_SIZE = 2
override val BASIC_LOAD_ADDRESS = 0x0801u
override val RAW_LOAD_ADDRESS = 0xc000u
@@ -42,7 +41,8 @@ class C64MachineDefinition: IMachineDefinition {
for(emulator in listOf("x64sc", "x64")) {
println("\nStarting C-64 emulator $emulator...")
val cmdline = listOf(emulator, "-silent", "-moncommands", "${programNameWithPath}.$viceMonListPostfix",
val viceMonlist = viceMonListName(programNameWithPath.toString())
val cmdline = listOf(emulator, "-silent", "-moncommands", viceMonlist,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process
@@ -63,16 +63,18 @@ class C64MachineDefinition: IMachineDefinition {
zeropage = C64Zeropage(compilerOptions)
}
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
override val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey",
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
"inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las",
"lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php",
"pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx",
"sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre",
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
override val opcodeNames = normal6502instructions
}
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
internal val normal6502instructions = setOf(
"adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey",
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
"inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las",
"lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php",
"pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx",
"sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre",
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
@@ -1,4 +1,4 @@
package prog8.compiler.target.c64
package prog8.codegen.target.c64
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.InternalCompilerException
@@ -10,7 +10,7 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
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
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
init {
@@ -22,8 +22,8 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
if (options.zeropage == ZeropageType.FULL) {
free.addAll(0x04u..0xf9u)
free.add(0xffu)
free.addAll(0x02u..0xffu)
free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1+1u, SCRATCH_W2, SCRATCH_W2+1u))
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) {
@@ -1,4 +1,4 @@
package prog8.compiler.target.cbm
package prog8.codegen.target.cbm
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
@@ -14,7 +14,7 @@ import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
internal const val viceMonListPostfix = "vice-mon-list"
internal fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"
class AssemblyProgram(
override val valid: Boolean,
@@ -25,17 +25,22 @@ class AssemblyProgram(
private val assemblyFile = outputDir.resolve("$name.asm")
private val prgFile = outputDir.resolve("$name.prg")
private val binFile = outputDir.resolve("$name.bin")
private val viceMonListFile = outputDir.resolve("$name.$viceMonListPostfix")
private val viceMonListFile = outputDir.resolve(viceMonListName(name))
private val listFile = outputDir.resolve("$name.list")
override fun assemble(quiet: Boolean, options: CompilationOptions): Int {
override fun assemble(options: CompilationOptions): Int {
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
"--dump-labels", "--vice-labels", "--labels=$viceMonListFile", "--no-monitor"
)
if(quiet)
if(options.asmQuiet)
command.add("--quiet")
if(options.asmListfile)
command.add("--list=$listFile")
val outFile = when (options.output) {
OutputType.PRG -> {
command.add("--cbm-prg")
@@ -1,4 +1,4 @@
package prog8.compiler.target.cbm
package prog8.codegen.target.cbm
import prog8.compilerinterface.IMachineFloat
import prog8.compilerinterface.InternalCompilerException
@@ -1,4 +1,4 @@
package prog8.compiler.target.cbm
package prog8.codegen.target.cbm
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
@@ -1,4 +1,4 @@
package prog8.compiler.target.cpu6502.codegen
package prog8.codegen.target.cpu6502.codegen
import com.github.michaelbull.result.fold
import prog8.ast.*
@@ -6,16 +6,17 @@ import prog8.ast.antlr.escape
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.cbm.AssemblyProgram
import prog8.compiler.target.cbm.loadAsmIncludeFile
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen
import prog8.compiler.target.cpu6502.codegen.assignment.SourceStorageKind
import prog8.codegen.target.AssemblyError
import prog8.codegen.target.C64Target
import prog8.codegen.target.Cx16Target
import prog8.codegen.target.cbm.AssemblyProgram
import prog8.codegen.target.cbm.loadAsmIncludeFile
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignSource
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignTarget
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.codegen.target.cpu6502.codegen.assignment.AssignmentAsmGen
import prog8.codegen.target.cpu6502.codegen.assignment.SourceStorageKind
import prog8.codegen.target.cpu6502.codegen.assignment.TargetStorageKind
import prog8.compilerinterface.*
import prog8.parser.SourceCode
import java.nio.file.Path
@@ -66,7 +67,6 @@ class AsmGen(private val program: Program,
throw AssemblyError("first block should be 'main'")
for(b in program.allBlocks) {
b.statements.removeAll(blockVariableInitializers.getValue(b)) // no longer output the initialization assignments as regular statements in the block!
block2asm(b)
}
@@ -136,8 +136,8 @@ class AsmGen(private val program: Program,
when {
options.launcher == LauncherType.BASIC -> {
if (program.actualLoadAddress != 0x0801u)
throw AssemblyError("BASIC output must have load address $0801")
if (program.actualLoadAddress != options.compTarget.machine.BASIC_LOAD_ADDRESS)
throw AssemblyError("BASIC output must have correct load address")
out("; ---- basic program with sys call ----")
out("* = ${program.actualLoadAddress.toHex()}")
val year = LocalDate.now().year
@@ -215,10 +215,30 @@ class AsmGen(private val program: Program,
out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
if(options.dontReinitGlobals) {
block.statements.filterIsInstance<VarDecl>().forEach {
it.zeropage = ZeropageWish.NOT_IN_ZEROPAGE
val init = it.nextSibling() as? Assignment
if(init?.target?.identifier?.targetVarDecl(program) === it) {
// put the init value back into the vardecl
it.value = init.value
}
}
}
// no longer output the initialization assignments as regular statements in the block!
block.statements.removeAll(blockVariableInitializers.getValue(block))
outputSourceLine(block)
zeropagevars2asm(block.statements, block)
memdefs2asm(block.statements, block)
vardecls2asm(block.statements, block)
block.statements.filterIsInstance<VarDecl>().forEach {
if(it.type==VarDeclType.VAR && it.datatype in NumericDatatypes)
it.value=null
}
out("\n; subroutines in this block")
// first translate regular statements, and then put the subroutines at the end.
@@ -226,12 +246,14 @@ class AsmGen(private val program: Program,
stmts.forEach { translate(it) }
subroutine.forEach { translateSubroutine(it as Subroutine) }
// generate subroutine to initialize block-level variables
val initializers = blockVariableInitializers.getValue(block)
if(initializers.isNotEmpty()) {
out("prog8_init_vars\t.proc\n")
initializers.forEach { assign -> translate(assign) }
out(" rts\n .pend")
if(!options.dontReinitGlobals) {
// generate subroutine to initialize block-level (global) variables
val initializers = blockVariableInitializers.getValue(block)
if (initializers.isNotEmpty()) {
out("prog8_init_vars\t.proc\n")
initializers.forEach { assign -> translate(assign) }
out(" rts\n .pend")
}
}
out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
@@ -290,15 +312,38 @@ class AsmGen(private val program: Program,
private fun vardecl2asm(decl: VarDecl) {
val name = decl.name
val value = decl.value
val staticValue: Number =
if(value!=null) {
if(value is NumericLiteralValue) {
if(value.type==DataType.FLOAT)
value.number
else
value.number.toInt()
} else {
if(decl.datatype in NumericDatatypes)
throw AssemblyError("can only deal with constant numeric values for global vars $value at ${decl.position}")
else 0
}
} else 0
when (decl.datatype) {
DataType.UBYTE -> out("$name\t.byte 0")
DataType.BYTE -> out("$name\t.char 0")
DataType.UWORD -> out("$name\t.word 0")
DataType.WORD -> out("$name\t.sint 0")
DataType.FLOAT -> out("$name\t.byte 0,0,0,0,0 ; float")
DataType.UBYTE -> out("$name\t.byte ${staticValue.toHex()}")
DataType.BYTE -> out("$name\t.char $staticValue")
DataType.UWORD -> out("$name\t.word ${staticValue.toHex()}")
DataType.WORD -> out("$name\t.sint $staticValue")
DataType.FLOAT -> {
if(staticValue==0) {
out("$name\t.byte 0,0,0,0,0 ; float")
} else {
val floatFill = compTarget.machine.getFloat(staticValue).makeFloatFillAsm()
out("$name\t.byte $floatFill ; float $staticValue")
}
}
DataType.STR -> {
val str = decl.value as StringLiteralValue
outputStringvar(decl, compTarget.encodeString(str.value, str.altEncoding).plus(0.toUByte()))
throw AssemblyError("all string vars should have been interned")
// val str = decl.value as StringLiteralValue
// outputStringvar(decl, compTarget.encodeString(str.value, str.altEncoding).plus(0.toUByte()))
}
DataType.ARRAY_UB -> {
val data = makeArrayFillDataUnsigned(decl)
@@ -391,7 +436,7 @@ class AsmGen(private val program: Program,
val vars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
val encodedstringVars = vars
.filter {it.datatype == DataType.STR }
.filter { it.datatype == DataType.STR && shouldActuallyOutputStringVar(it) }
.map {
val str = it.value as StringLiteralValue
it to compTarget.encodeString(str.value, str.altEncoding).plus(0.toUByte())
@@ -412,6 +457,17 @@ class AsmGen(private val program: Program,
}
}
private fun shouldActuallyOutputStringVar(strvar: VarDecl): Boolean {
if(strvar.sharedWithAsm)
return true
val uses = callGraph.usages(strvar)
val onlyInMemoryFuncs = uses.all {
val builtinfunc = (it.parent as? IFunctionCall)?.target?.targetStatement(program) as? BuiltinFunctionPlaceholder
builtinfunc?.name=="memory"
}
return !onlyInMemoryFuncs
}
private fun outputStringvar(strdecl: VarDecl, bytes: List<UByte>) {
val sv = strdecl.value as StringLiteralValue
val altEncoding = if(sv.altEncoding) "@" else ""
@@ -759,7 +815,7 @@ class AsmGen(private val program: Program,
outputSourceLine(stmt)
when(stmt) {
is VarDecl -> translate(stmt)
is NopStatement -> {}
is Nop -> {}
is Directive -> translate(stmt)
is Return -> translate(stmt)
is Subroutine -> translateSubroutine(stmt)
@@ -774,24 +830,21 @@ class AsmGen(private val program: Program,
}
}
is Assignment -> assignmentAsmGen.translate(stmt)
is Jump -> translate(stmt)
is Jump -> jmp(getJumpTarget(stmt))
is GoSub -> translate(stmt)
is PostIncrDecr -> postincrdecrAsmGen.translate(stmt)
is Label -> translate(stmt)
is BranchStatement -> translate(stmt)
is IfStatement -> translate(stmt)
is Branch -> translate(stmt)
is IfElse -> translate(stmt)
is ForLoop -> forloopsAsmGen.translate(stmt)
is Break -> {
if(loopEndLabels.isEmpty())
throw AssemblyError("break statement out of context ${stmt.position}")
jmp(loopEndLabels.peek())
}
is WhileLoop -> translate(stmt)
is RepeatLoop -> translate(stmt)
is UntilLoop -> translate(stmt)
is WhenStatement -> translate(stmt)
is BuiltinFunctionStatementPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore?")
is When -> translate(stmt)
is AnonymousScope -> translate(stmt)
is BuiltinFunctionPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore")
is UntilLoop -> throw AssemblyError("do..until should have been converted to jumps")
is WhileLoop -> throw AssemblyError("while should have been converted to jumps")
is Block -> throw AssemblyError("block should have been handled elsewhere")
is Break -> throw AssemblyError("break should have been replaced by goto")
else -> throw AssemblyError("missing asm translation for $stmt")
}
}
@@ -892,11 +945,11 @@ class AsmGen(private val program: Program,
internal fun translateExpression(expression: Expression) =
expressionsAsmGen.translateExpression(expression)
internal fun translateBuiltinFunctionCallExpression(functionCall: FunctionCall, signature: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature, resultToStack, resultRegister)
internal fun translateBuiltinFunctionCallExpression(functionCallExpr: FunctionCallExpr, signature: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCallExpr, signature, resultToStack, resultRegister)
internal fun translateFunctionCall(functionCall: FunctionCall, isExpression: Boolean) =
functioncallAsmGen.translateFunctionCall(functionCall, isExpression)
internal fun translateFunctionCall(functionCallExpr: FunctionCallExpr, isExpression: Boolean) =
functioncallAsmGen.translateFunctionCall(functionCallExpr, isExpression)
internal fun saveXbeforeCall(functionCall: IFunctionCall) =
functioncallAsmGen.saveXbeforeCall(functionCall)
@@ -1003,9 +1056,11 @@ class AsmGen(private val program: Program,
if(sub.name=="start" && sub.definingBlock.name=="main") {
out("; program startup initialization")
out(" cld")
blockVariableInitializers.forEach {
if(it.value.isNotEmpty())
out(" jsr ${it.key.name}.prog8_init_vars")
if(!options.dontReinitGlobals) {
blockVariableInitializers.forEach {
if (it.value.isNotEmpty())
out(" jsr ${it.key.name}.prog8_init_vars")
}
}
out("""
tsx
@@ -1015,6 +1070,25 @@ class AsmGen(private val program: Program,
clc""")
}
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
out("; simple int arg(s) passed via register(s)")
if(sub.parameters.size==1) {
val dt = sub.parameters[0].type
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, dt, sub, variableAsmName = sub.parameters[0].name)
if(dt in ByteDatatypes)
assignRegister(RegisterOrPair.A, target)
else
assignRegister(RegisterOrPair.AY, target)
} else {
require(sub.parameters.size==2)
// 2 simple byte args, first in A, second in Y
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name)
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name)
assignRegister(RegisterOrPair.A, target1)
assignRegister(RegisterOrPair.Y, target2)
}
}
if(!onlyVariables) {
out("; statements")
sub.statements.forEach { translate(it) }
@@ -1077,21 +1151,26 @@ class AsmGen(private val program: Program,
}
}
private fun translate(stmt: IfStatement) {
private fun translate(stmt: IfElse) {
requireComparisonExpression(stmt.condition) // IfStatement: condition must be of form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression
if (stmt.elsepart.isEmpty()) {
val endLabel = makeLabel("if_end")
translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
translate(stmt.truepart)
out(endLabel)
val jump = stmt.truepart.statements.singleOrNull()
if(jump is Jump) {
translateCompareAndJumpIfTrue(booleanCondition, jump)
} else {
val endLabel = makeLabel("if_end")
translateCompareAndJumpIfFalse(booleanCondition, endLabel)
translate(stmt.truepart)
out(endLabel)
}
}
else {
// both true and else parts
val elseLabel = makeLabel("if_else")
val endLabel = makeLabel("if_end")
translateComparisonExpressionWithJumpIfFalse(booleanCondition, elseLabel)
translateCompareAndJumpIfFalse(booleanCondition, elseLabel)
translate(stmt.truepart)
jmp(endLabel)
out(elseLabel)
@@ -1101,18 +1180,18 @@ class AsmGen(private val program: Program,
}
private fun requireComparisonExpression(condition: Expression) {
if(condition !is BinaryExpression || condition.operator !in comparisonOperators)
if(condition !is BinaryExpression || condition.operator !in ComparisonOperators)
throw AssemblyError("expected boolean comparison expression $condition")
}
private fun translate(stmt: RepeatLoop) {
val repeatLabel = makeLabel("repeat")
val endLabel = makeLabel("repeatend")
loopEndLabels.push(endLabel)
when (stmt.iterations) {
null -> {
// endless loop
val repeatLabel = makeLabel("repeat")
out(repeatLabel)
translate(stmt.body)
jmp(repeatLabel)
@@ -1120,18 +1199,12 @@ class AsmGen(private val program: Program,
}
is NumericLiteralValue -> {
val iterations = (stmt.iterations as NumericLiteralValue).number.toInt()
if(iterations<0 || iterations > 65535)
throw AssemblyError("invalid number of iterations")
when {
iterations == 0 -> {}
iterations <= 256 -> {
out(" lda #${iterations and 255}")
repeatByteCountInA(iterations, repeatLabel, endLabel, stmt)
}
else -> {
out(" lda #<${iterations} | ldy #>${iterations}")
repeatWordCountInAY(iterations, repeatLabel, endLabel, stmt)
}
iterations == 1 -> translate(stmt.body)
iterations<0 || iterations>65535 -> throw AssemblyError("invalid number of iterations")
iterations <= 256 -> repeatByteCount(iterations, stmt)
else -> repeatWordCount(iterations, stmt)
}
}
is IdentifierReference -> {
@@ -1139,12 +1212,12 @@ class AsmGen(private val program: Program,
val name = asmVariableName(stmt.iterations as IdentifierReference)
when(vardecl.datatype) {
DataType.UBYTE, DataType.BYTE -> {
assignVariableToRegister(name, RegisterOrPair.A)
repeatByteCountInA(null, repeatLabel, endLabel, stmt)
assignVariableToRegister(name, RegisterOrPair.Y)
repeatCountInY(stmt, endLabel)
}
DataType.UWORD, DataType.WORD -> {
assignVariableToRegister(name, RegisterOrPair.AY)
repeatWordCountInAY(null, repeatLabel, endLabel, stmt)
repeatWordCountInAY(endLabel, stmt)
}
else -> throw AssemblyError("invalid loop variable datatype $vardecl")
}
@@ -1155,12 +1228,12 @@ class AsmGen(private val program: Program,
throw AssemblyError("unknown dt")
when (dt.getOr(DataType.UNDEFINED)) {
in ByteDatatypes -> {
assignExpressionToRegister(stmt.iterations!!, RegisterOrPair.A)
repeatByteCountInA(null, repeatLabel, endLabel, stmt)
assignExpressionToRegister(stmt.iterations!!, RegisterOrPair.Y)
repeatCountInY(stmt, endLabel)
}
in WordDatatypes -> {
assignExpressionToRegister(stmt.iterations!!, RegisterOrPair.AY)
repeatWordCountInAY(null, repeatLabel, endLabel, stmt)
repeatWordCountInAY(endLabel, stmt)
}
else -> throw AssemblyError("invalid loop expression datatype $dt")
}
@@ -1170,13 +1243,69 @@ class AsmGen(private val program: Program,
loopEndLabels.pop()
}
private fun repeatWordCountInAY(constIterations: Int?, repeatLabel: String, endLabel: String, stmt: RepeatLoop) {
// note: A/Y must have been loaded with the number of iterations!
if(constIterations==0)
return
// no need to explicitly test for 0 iterations as this is done in the countdown logic below
private fun repeatWordCount(count: Int, stmt: RepeatLoop) {
require(count in 257..65535)
val repeatLabel = makeLabel("repeat")
if(isTargetCpu(CpuType.CPU65c02)) {
val counterVar = createRepeatCounterVar(DataType.UWORD, true, stmt)
if(counterVar!=null) {
out("""
lda #<$count
ldy #>$count
sta $counterVar
sty $counterVar+1
$repeatLabel""")
translate(stmt.body)
out("""
lda $counterVar
bne +
dec $counterVar+1
+ dec $counterVar
lda $counterVar
ora $counterVar+1
bne $repeatLabel""")
} else {
out("""
lda #<$count
ldy #>$count
$repeatLabel pha
phy""")
translate(stmt.body)
out("""
ply
pla
bne +
dey
+ dea
bne $repeatLabel
cpy #0
bne $repeatLabel""")
}
} else {
val counterVar = createRepeatCounterVar(DataType.UWORD, false, stmt)
out("""
lda #<$count
ldy #>$count
sta $counterVar
sty $counterVar+1
$repeatLabel""")
translate(stmt.body)
out("""
lda $counterVar
bne +
dec $counterVar+1
+ dec $counterVar
lda $counterVar
ora $counterVar+1
bne $repeatLabel""")
}
}
val counterVar: String = createRepeatCounterVar(DataType.UWORD, constIterations, stmt)
private fun repeatWordCountInAY(endLabel: String, stmt: RepeatLoop) {
// note: A/Y must have been loaded with the number of iterations!
// no need to explicitly test for 0 iterations as this is done in the countdown logic below
val repeatLabel = makeLabel("repeat")
val counterVar = createRepeatCounterVar(DataType.UWORD, false, stmt)!!
out("""
sta $counterVar
sty $counterVar+1
@@ -1194,23 +1323,58 @@ $repeatLabel lda $counterVar
out(endLabel)
}
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, stmt: RepeatLoop) {
// note: A must be loaded with the number of iterations!
if(constIterations==0)
return
if(constIterations==null)
out(" beq $endLabel ; skip loop if zero iters")
val counterVar = createRepeatCounterVar(DataType.UBYTE, constIterations, stmt)
out(" sta $counterVar")
out(repeatLabel)
translate(stmt.body)
out(" dec $counterVar | bne $repeatLabel")
if(constIterations==null)
out(endLabel)
private fun repeatByteCount(count: Int, stmt: RepeatLoop) {
require(count in 2..256)
val repeatLabel = makeLabel("repeat")
if(isTargetCpu(CpuType.CPU65c02)) {
val counterVar = createRepeatCounterVar(DataType.UBYTE, true, stmt)
if(counterVar!=null) {
out(" lda #${count and 255} | sta $counterVar")
out(repeatLabel)
translate(stmt.body)
out(" dec $counterVar | bne $repeatLabel")
} else {
out(" ldy #${count and 255}")
out("$repeatLabel phy")
translate(stmt.body)
out(" ply | dey | bne $repeatLabel")
}
} else {
val counterVar = createRepeatCounterVar(DataType.UBYTE, false, stmt)
out(" lda #${count and 255} | sta $counterVar")
out(repeatLabel)
translate(stmt.body)
out(" dec $counterVar | bne $repeatLabel")
}
}
private fun createRepeatCounterVar(dt: DataType, constIterations: Int?, stmt: RepeatLoop): String {
private fun repeatCountInY(stmt: RepeatLoop, endLabel: String) {
// note: Y must just have been loaded with the (variable) number of loops to be performed!
val repeatLabel = makeLabel("repeat")
if(isTargetCpu(CpuType.CPU65c02)) {
val counterVar = createRepeatCounterVar(DataType.UBYTE, true, stmt)
if(counterVar!=null) {
out(" beq $endLabel | sty $counterVar")
out(repeatLabel)
translate(stmt.body)
out(" dec $counterVar | bne $repeatLabel")
} else {
out(" beq $endLabel")
out("$repeatLabel phy")
translate(stmt.body)
out(" ply | dey | bne $repeatLabel")
}
} else {
val counterVar = createRepeatCounterVar(DataType.UBYTE, false, stmt)!!
out(" beq $endLabel | sty $counterVar")
out(repeatLabel)
translate(stmt.body)
out(" dec $counterVar | bne $repeatLabel")
}
out(endLabel)
}
private fun createRepeatCounterVar(dt: DataType, mustBeInZeropage: Boolean, stmt: RepeatLoop): String? {
val asmInfo = stmt.definingSubroutine!!.asmGenInfo
var parent = stmt.parent
while(parent !is ParentSentinel) {
@@ -1223,27 +1387,33 @@ $repeatLabel lda $counterVar
if(!isNested) {
// we can re-use a counter var from the subroutine if it already has one for that datatype
val existingVar = asmInfo.extraVars.firstOrNull { it.first==dt }
if(existingVar!=null)
return existingVar.second
if(existingVar!=null) {
if(!mustBeInZeropage || existingVar.third!=null)
return existingVar.second
}
}
val counterVar = makeLabel("repeatcounter")
val counterVar = makeLabel("counter")
when(dt) {
DataType.UBYTE -> {
if(constIterations!=null && constIterations>=16 && zeropage.hasByteAvailable()) {
if(zeropage.hasByteAvailable()) {
// allocate count var on ZP
val zpAddr = zeropage.allocate(counterVar, DataType.UBYTE, stmt.position, errors)
asmInfo.extraVars.add(Triple(DataType.UBYTE, counterVar, zpAddr))
} else {
if(mustBeInZeropage)
return null
asmInfo.extraVars.add(Triple(DataType.UBYTE, counterVar, null))
}
}
DataType.UWORD -> {
if(constIterations!=null && constIterations>=16 && zeropage.hasWordAvailable()) {
if(zeropage.hasWordAvailable()) {
// allocate count var on ZP
val zpAddr = zeropage.allocate(counterVar, DataType.UWORD, stmt.position, errors)
asmInfo.extraVars.add(Triple(DataType.UWORD, counterVar, zpAddr))
} else {
if(mustBeInZeropage)
return null
asmInfo.extraVars.add(Triple(DataType.UWORD, counterVar, null))
}
}
@@ -1252,36 +1422,7 @@ $repeatLabel lda $counterVar
return counterVar
}
private fun translate(stmt: WhileLoop) {
requireComparisonExpression(stmt.condition) // WhileLoop: condition must be of form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression
val whileLabel = makeLabel("while")
val endLabel = makeLabel("whileend")
loopEndLabels.push(endLabel)
out(whileLabel)
translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
translate(stmt.body)
jmp(whileLabel)
out(endLabel)
loopEndLabels.pop()
}
private fun translate(stmt: UntilLoop) {
requireComparisonExpression(stmt.condition) // UntilLoop: condition must be of form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression
val repeatLabel = makeLabel("repeat")
val endLabel = makeLabel("repeatend")
loopEndLabels.push(endLabel)
out(repeatLabel)
translate(stmt.body)
translateComparisonExpressionWithJumpIfFalse(booleanCondition, repeatLabel)
out(endLabel)
loopEndLabels.pop()
}
private fun translate(stmt: WhenStatement) {
private fun translate(stmt: When) {
val endLabel = makeLabel("choice_end")
val choiceBlocks = mutableListOf<Pair<String, AnonymousScope>>()
val conditionDt = stmt.condition.inferType(program)
@@ -1335,47 +1476,23 @@ $repeatLabel lda $counterVar
scope.statements.forEach{ translate(it) }
}
private fun translate(stmt: BranchStatement) {
private fun translate(stmt: Branch) {
if(stmt.truepart.isEmpty() && stmt.elsepart.isNotEmpty())
throw AssemblyError("only else part contains code, shoud have been switched already")
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)}")
translate(stmt.elsepart)
} else {
val truePartIsJustBreak = stmt.truepart.statements.firstOrNull() is Break
val elsePartIsJustBreak = stmt.elsepart.statements.firstOrNull() is Break
if(stmt.elsepart.isEmpty()) {
if(truePartIsJustBreak) {
// branch with just a break (jump out of loop)
val instruction = branchInstruction(stmt.condition, false)
val loopEndLabel = loopEndLabels.peek()
out(" $instruction $loopEndLabel")
} else {
val instruction = branchInstruction(stmt.condition, true)
val elseLabel = makeLabel("branch_else")
out(" $instruction $elseLabel")
translate(stmt.truepart)
out(elseLabel)
}
}
else if(truePartIsJustBreak) {
// branch with just a break (jump out of loop)
val instruction = branchInstruction(stmt.condition, false)
val loopEndLabel = loopEndLabels.peek()
out(" $instruction $loopEndLabel")
translate(stmt.elsepart)
} else if(elsePartIsJustBreak) {
// branch with just a break (jump out of loop) but true/false inverted
val instruction = branchInstruction(stmt.condition, true)
val loopEndLabel = loopEndLabels.peek()
out(" $instruction $loopEndLabel")
val elseLabel = makeLabel("branch_else")
out(" $instruction $elseLabel")
translate(stmt.truepart)
out(elseLabel)
} else {
val instruction = branchInstruction(stmt.condition, true)
val elseLabel = makeLabel("branch_else")
@@ -1432,21 +1549,16 @@ $label nop""")
}
}
private fun translate(jump: Jump) {
if(jump.isGosub) {
jump as GoSub
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)
out(" jsr ${getJumpTarget(jump)}")
restoreXafterCall(jump)
}
private fun translate(gosub: GoSub) {
val tgt = gosub.identifier!!.targetSubroutine(program)
if(tgt!=null && tgt.isAsmSubroutine) {
// no need to rescue X , this has been taken care of already
out(" jsr ${getJumpTarget(gosub)}")
} else {
saveXbeforeCall(gosub)
out(" jsr ${getJumpTarget(gosub)}")
restoreXafterCall(gosub)
}
else
jmp(getJumpTarget(jump))
}
private fun getJumpTarget(jump: Jump): String {
@@ -1461,6 +1573,18 @@ $label nop""")
}
}
private fun getJumpTarget(gosub: GoSub): String {
val ident = gosub.identifier
val label = gosub.generatedLabel
val addr = gosub.address
return when {
ident!=null -> asmSymbolName(ident)
label!=null -> label
addr!=null -> addr.toHex()
else -> "????"
}
}
private fun translate(ret: Return, withRts: Boolean=true) {
ret.value?.let { returnvalue ->
val sub = ret.definingSubroutine!!
@@ -1644,41 +1768,46 @@ $label nop""")
return false
}
private fun translateCompareAndJumpIfTrue(expr: BinaryExpression, jump: Jump) {
if(expr.operator !in ComparisonOperators)
throw AssemblyError("must be comparison expression")
// invert the comparison, so we can reuse the JumpIfFalse code generation routines
val invertedComparisonOperator = invertedComparisonOperator(expr.operator)
?: throw AssemblyError("can't invert comparison $expr")
private fun translateComparisonExpressionWithJumpIfFalse(expr: BinaryExpression, jumpIfFalseLabel: String) {
// This is a helper routine called from while, do-util, and if expressions to generate optimized conditional branching code.
// First, if it is of the form: <constvalue> <comparison> X , then flip the expression so the constant is always the right operand.
val left = expr.left
val right = expr.right
val rightConstVal = right.constValue(program)
var left = expr.left
var right = expr.right
var operator = expr.operator
var leftConstVal = left.constValue(program)
var rightConstVal = right.constValue(program)
// make sure the constant value is on the right of the comparison expression
if(leftConstVal!=null) {
val tmp = left
left = right
right = tmp
val tmp2 = leftConstVal
leftConstVal = rightConstVal
rightConstVal = tmp2
when(expr.operator) {
"<" -> operator = ">"
"<=" -> operator = ">="
">" -> operator = "<"
">=" -> operator = "<="
}
val label = when {
jump.generatedLabel!=null -> jump.generatedLabel!!
jump.identifier!=null -> asmSymbolName(jump.identifier!!)
jump.address!=null -> jump.address!!.toHex()
else -> throw AssemblyError("weird jump")
}
if (rightConstVal!=null && rightConstVal.number == 0.0)
jumpIfZeroOrNot(left, operator, jumpIfFalseLabel)
else
jumpIfComparison(left, operator, right, jumpIfFalseLabel, leftConstVal, rightConstVal)
testZeroAndJump(left, invertedComparisonOperator, label)
else {
val leftConstVal = left.constValue(program)
testNonzeroComparisonAndJump(left, invertedComparisonOperator, right, label, leftConstVal, rightConstVal)
}
}
private fun jumpIfZeroOrNot(
private fun translateCompareAndJumpIfFalse(expr: BinaryExpression, jumpIfFalseLabel: String) {
val left = expr.left
val right = expr.right
val operator = expr.operator
val leftConstVal = left.constValue(program)
val rightConstVal = right.constValue(program)
if (rightConstVal!=null && rightConstVal.number == 0.0)
testZeroAndJump(left, operator, jumpIfFalseLabel)
else
testNonzeroComparisonAndJump(left, operator, right, jumpIfFalseLabel, leftConstVal, rightConstVal)
}
private fun testZeroAndJump(
left: Expression,
operator: String,
jumpIfFalseLabel: String
@@ -1693,7 +1822,7 @@ $label nop""")
}
if(dt==DataType.UBYTE) {
assignExpressionToRegister(left, RegisterOrPair.A, false)
if (left is FunctionCall && !left.isSimple)
if (left is FunctionCallExpr && !left.isSimple)
out(" cmp #0")
} else {
assignExpressionToRegister(left, RegisterOrPair.AY, false)
@@ -1709,7 +1838,7 @@ $label nop""")
}
DataType.BYTE -> {
assignExpressionToRegister(left, RegisterOrPair.A, true)
if (left is FunctionCall && !left.isSimple)
if (left is FunctionCallExpr && !left.isSimple)
out(" cmp #0")
when (operator) {
"==" -> out(" bne $jumpIfFalseLabel")
@@ -1767,7 +1896,7 @@ $label nop""")
}
}
private fun jumpIfComparison(
private fun testNonzeroComparisonAndJump(
left: Expression,
operator: String,
right: Expression,
@@ -2038,7 +2167,7 @@ $label nop""")
if (left is IdentifierReference) {
return if(rightConstVal.number.toInt()!=0) {
assignExpressionToRegister(left, RegisterOrPair.A)
code("#${rightConstVal.number}")
code("#${rightConstVal.number.toInt()}")
}
else
jmp(jumpIfFalseLabel)
@@ -2046,7 +2175,7 @@ $label nop""")
else if (left is DirectMemoryRead) {
return if(rightConstVal.number.toInt()!=0) {
translateDirectMemReadExpressionToRegAorStack(left, false)
code("#${rightConstVal.number}")
code("#${rightConstVal.number.toInt()}")
}
else
jmp(jumpIfFalseLabel)
@@ -2118,7 +2247,7 @@ $label nop""")
if (left is IdentifierReference) {
return if(rightConstVal.number.toInt()!=0) {
assignExpressionToRegister(left, RegisterOrPair.AY)
code("#>${rightConstVal.number}", "#<${rightConstVal.number}")
code("#>${rightConstVal.number.toInt()}", "#<${rightConstVal.number.toInt()}")
}
else
jmp(jumpIfFalseLabel)
@@ -2155,7 +2284,7 @@ $label nop""")
if (left is IdentifierReference) {
return if(rightConstVal.number.toInt()!=0) {
assignExpressionToRegister(left, RegisterOrPair.AY)
code("#>${rightConstVal.number}", "#<${rightConstVal.number}")
code("#>${rightConstVal.number.toInt()}", "#<${rightConstVal.number.toInt()}")
}
else {
val name = asmVariableName(left)
@@ -2191,14 +2320,14 @@ $label nop""")
if (left is IdentifierReference) {
assignExpressionToRegister(left, RegisterOrPair.A)
return if(rightConstVal.number.toInt()!=0)
code("#${rightConstVal.number}")
code("#${rightConstVal.number.toInt()}")
else
out(" beq $jumpIfFalseLabel")
}
else if (left is DirectMemoryRead) {
translateDirectMemReadExpressionToRegAorStack(left, false)
return if(rightConstVal.number.toInt()!=0)
code("#${rightConstVal.number}")
code("#${rightConstVal.number.toInt()}")
else
out(" beq $jumpIfFalseLabel")
}
@@ -2272,7 +2401,7 @@ $label nop""")
if (left is IdentifierReference) {
return if(rightConstVal.number.toInt()!=0) {
assignExpressionToRegister(left, RegisterOrPair.AY)
code("#>${rightConstVal.number}", "#<${rightConstVal.number}")
code("#>${rightConstVal.number.toInt()}", "#<${rightConstVal.number.toInt()}")
}
else {
val name = asmVariableName(left)
@@ -2356,14 +2485,14 @@ $label nop""")
if (left is IdentifierReference) {
assignExpressionToRegister(left, RegisterOrPair.A)
return if(rightConstVal.number.toInt()!=0)
code("#${rightConstVal.number}")
code("#${rightConstVal.number.toInt()}")
else
out(" bne $jumpIfFalseLabel")
}
else if (left is DirectMemoryRead) {
translateDirectMemReadExpressionToRegAorStack(left, false)
return if(rightConstVal.number.toInt()!=0)
code("#${rightConstVal.number}")
code("#${rightConstVal.number.toInt()}")
else
out(" bne $jumpIfFalseLabel")
}
@@ -2439,7 +2568,7 @@ $label nop""")
if (left is IdentifierReference) {
return if(rightConstVal.number.toInt()!=0) {
assignExpressionToRegister(left, RegisterOrPair.AY)
code("#>${rightConstVal.number}", "#<${rightConstVal.number}")
code("#>${rightConstVal.number.toInt()}", "#<${rightConstVal.number.toInt()}")
}
else {
val name = asmVariableName(left)
@@ -2523,14 +2652,14 @@ $label nop""")
if (left is IdentifierReference) {
if(rightConstVal.number.toInt()!=0) {
assignExpressionToRegister(left, RegisterOrPair.A)
code("#${rightConstVal.number}")
code("#${rightConstVal.number.toInt()}")
}
return
}
else if (left is DirectMemoryRead) {
if(rightConstVal.number.toInt()!=0) {
translateDirectMemReadExpressionToRegAorStack(left, false)
code("#${rightConstVal.number}")
code("#${rightConstVal.number.toInt()}")
}
return
}
@@ -2600,7 +2729,7 @@ $label nop""")
if (left is IdentifierReference) {
if(rightConstVal.number.toInt()!=0) {
assignExpressionToRegister(left, RegisterOrPair.AY)
code("#>${rightConstVal.number}", "#<${rightConstVal.number}")
code("#>${rightConstVal.number.toInt()}", "#<${rightConstVal.number.toInt()}")
}
}
}
@@ -2635,7 +2764,7 @@ $label nop""")
if (left is IdentifierReference) {
return if(rightConstVal.number.toInt()!=0) {
assignExpressionToRegister(left, RegisterOrPair.AY)
code("#>${rightConstVal.number}", "#<${rightConstVal.number}")
code("#>${rightConstVal.number.toInt()}", "#<${rightConstVal.number.toInt()}")
}
else {
val name = asmVariableName(left)
@@ -2667,14 +2796,14 @@ $label nop""")
if (left is IdentifierReference) {
assignExpressionToRegister(left, RegisterOrPair.A)
return if(rightConstVal.number.toInt()!=0)
code("#${rightConstVal.number}")
code("#${rightConstVal.number.toInt()}")
else
out(" bne $jumpIfFalseLabel")
}
else if (left is DirectMemoryRead) {
translateDirectMemReadExpressionToRegAorStack(left, false)
return if(rightConstVal.number.toInt()!=0)
code("#${rightConstVal.number}")
code("#${rightConstVal.number.toInt()}")
else
out(" bne $jumpIfFalseLabel")
}
@@ -2704,14 +2833,14 @@ $label nop""")
if (left is IdentifierReference) {
assignExpressionToRegister(left, RegisterOrPair.A)
return if(rightConstVal.number.toInt()!=0)
code("#${rightConstVal.number}")
code("#${rightConstVal.number.toInt()}")
else
out(" beq $jumpIfFalseLabel")
}
else if (left is DirectMemoryRead) {
translateDirectMemReadExpressionToRegAorStack(left, false)
return if(rightConstVal.number.toInt()!=0)
code("#${rightConstVal.number}")
code("#${rightConstVal.number.toInt()}")
else
out(" beq $jumpIfFalseLabel")
}
@@ -2735,20 +2864,23 @@ $label nop""")
} else {
if (left is IdentifierReference) {
val name = asmVariableName(left)
if(rightConstVal.number.toInt()!=0)
if(rightConstVal.number!=0.0) {
val rightNum = rightConstVal.number.toHex()
out("""
lda $name
cmp #<${rightConstVal.number}
cmp #<$rightNum
bne $jumpIfFalseLabel
lda $name+1
cmp #>${rightConstVal.number}
cmp #>$rightNum
bne $jumpIfFalseLabel""")
else
}
else {
out("""
lda $name
bne $jumpIfFalseLabel
lda $name+1
bne $jumpIfFalseLabel""")
}
return
}
}
@@ -2807,15 +2939,17 @@ $label nop""")
} else {
if (left is IdentifierReference) {
val name = asmVariableName(left)
if(rightConstVal.number.toInt()!=0)
if(rightConstVal.number.toInt()!=0) {
val number = rightConstVal.number.toHex()
out("""
lda $name
cmp #<${rightConstVal.number}
cmp #<$number
bne +
lda $name+1
cmp #>${rightConstVal.number}
cmp #>$number
beq $jumpIfFalseLabel
+""")
}
else
out("""
lda $name
@@ -1,4 +1,4 @@
package prog8.compiler.target.cpu6502.codegen
package prog8.codegen.target.cpu6502.codegen
import prog8.ast.Program
import prog8.ast.base.VarDeclType
@@ -1,4 +1,4 @@
package prog8.compiler.target.cpu6502.codegen
package prog8.codegen.target.cpu6502.codegen
import prog8.ast.base.Cx16VirtualRegisters
import prog8.ast.base.RegisterOrPair
@@ -44,7 +44,7 @@ internal fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>,
it.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
}
}
is FunctionCall -> {
is FunctionCallExpr -> {
if (expr.target.nameInSource == listOf("lsb") || expr.target.nameInSource == listOf("msb"))
return isClobberRisk(expr.args[0])
if (expr.target.nameInSource == listOf("mkword"))
@@ -1,4 +1,4 @@
package prog8.compiler.target.cpu6502.codegen
package prog8.codegen.target.cpu6502.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Node
@@ -10,16 +10,16 @@ import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.cpu6502.codegen.assignment.*
import prog8.codegen.target.AssemblyError
import prog8.codegen.target.Cx16Target
import prog8.codegen.target.cpu6502.codegen.assignment.*
import prog8.compilerinterface.CpuType
import prog8.compilerinterface.FSignature
import prog8.compilerinterface.subroutineFloatEvalResultVar2
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val assignAsmGen: AssignmentAsmGen) {
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
internal fun translateFunctioncallExpression(fcall: FunctionCallExpr, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
translateFunctioncall(fcall, func, discardResult = false, resultToStack = resultToStack, resultRegister = resultRegister)
}
@@ -358,7 +358,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
is NumericLiteralValue -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp #${arg2.number}")
asmgen.out(" cmp #${arg2.number.toInt()}")
}
is DirectMemoryRead -> {
if(arg2.addressExpression is NumericLiteralValue) {
@@ -393,9 +393,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
is NumericLiteralValue -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
cpy #>${arg2.number}
cpy #>${arg2.number.toInt()}
bne +
cmp #<${arg2.number}
cmp #<${arg2.number.toInt()}
+""")
}
else -> {
@@ -414,10 +414,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if(discardResult || fcall !is FunctionCall)
if(discardResult || fcall !is FunctionCallExpr)
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
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
val size = (fcall.args[1] as NumericLiteralValue).number.toUInt()
val existingSize = asmgen.slabs[name]
@@ -1,12 +1,12 @@
package prog8.compiler.target.cpu6502.codegen
package prog8.codegen.target.cpu6502.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.BuiltinFunctionPlaceholder
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.target.AssemblyError
import prog8.codegen.target.AssemblyError
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType
import kotlin.math.absoluteValue
@@ -36,18 +36,20 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
is DirectMemoryRead -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
is NumericLiteralValue -> translateExpression(expression)
is IdentifierReference -> translateExpression(expression)
is FunctionCall -> translateFunctionCallResultOntoStack(expression)
is FunctionCallExpr -> translateFunctionCallResultOntoStack(expression)
is ContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported")
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
is CharLiteral -> throw AssemblyError("charliteral should have been replaced by ubyte using certain encoding")
else -> TODO("missing expression asmgen for $expression")
}
}
private fun translateFunctionCallResultOntoStack(call: FunctionCall) {
private fun translateFunctionCallResultOntoStack(call: FunctionCallExpr) {
// only for use in nested expression evaluation
val sub = call.target.targetStatement(program)
if(sub is BuiltinFunctionStatementPlaceholder) {
if(sub is BuiltinFunctionPlaceholder) {
val builtinFunc = BuiltinFunctions.getValue(sub.name)
asmgen.translateBuiltinFunctionCallExpression(call, builtinFunc, true, null)
} else {
@@ -543,7 +545,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
if(leftDt==DataType.STR && rightDt==DataType.STR && expr.operator in comparisonOperators) {
if(leftDt==DataType.STR && rightDt==DataType.STR && expr.operator in ComparisonOperators) {
translateCompareStrings(expr.left, expr.operator, expr.right)
}
else {
@@ -1,4 +1,4 @@
package prog8.compiler.target.cpu6502.codegen
package prog8.codegen.target.cpu6502.codegen
import prog8.ast.Program
import prog8.ast.base.ArrayToElementTypes
@@ -8,8 +8,7 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.ForLoop
import prog8.ast.toHex
import prog8.compiler.target.AssemblyError
import prog8.compilerinterface.toConstantIntegerRange
import prog8.codegen.target.AssemblyError
import kotlin.math.absoluteValue
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
@@ -1,4 +1,4 @@
package prog8.compiler.target.cpu6502.codegen
package prog8.codegen.target.cpu6502.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Node
@@ -6,11 +6,11 @@ import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
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
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind
import prog8.codegen.target.AssemblyError
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignSource
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignTarget
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.codegen.target.cpu6502.codegen.assignment.TargetStorageKind
import prog8.compilerinterface.CpuType
@@ -67,6 +67,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
}
internal fun optimizeIntArgsViaRegisters(sub: Subroutine) =
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
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!
@@ -76,8 +80,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}")
val subAsmName = asmgen.asmSymbolName(call.target)
if(!isExpression && !sub.isAsmSubroutine)
throw AssemblyError("functioncall statements to non-asmsub should have been replaced by GoSub $call")
if(!isExpression && !sub.isAsmSubroutine) {
if(!optimizeIntArgsViaRegisters(sub))
throw AssemblyError("functioncall statements to non-asmsub should have been replaced by GoSub $call")
}
if(sub.isAsmSubroutine) {
argumentsViaRegisters(sub, call)
@@ -98,7 +104,22 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
if(sub.inline)
throw AssemblyError("can only reliably inline asmsub routines at this time")
argumentsViaVariables(sub, call)
if(optimizeIntArgsViaRegisters(sub)) {
if(sub.parameters.size==1) {
val register = if (sub.parameters[0].type in ByteDatatypes) RegisterOrPair.A else RegisterOrPair.AY
argumentViaRegister(sub, IndexedValue(0, sub.parameters[0]), call.args[0], register)
} else {
// 2 byte params, second in Y, first in A
argumentViaRegister(sub, IndexedValue(0, sub.parameters[0]), call.args[0], RegisterOrPair.A)
if(!call.args[1].isSimple)
asmgen.out(" pha")
argumentViaRegister(sub, IndexedValue(1, sub.parameters[1]), call.args[1], RegisterOrPair.Y)
if(!call.args[1].isSimple)
asmgen.out(" pla")
}
} else {
argumentsViaVariables(sub, call)
}
asmgen.out(" jsr $subAsmName")
}
@@ -262,14 +283,14 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
}
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression, registerOverride: RegisterOrPair? = null) {
// pass argument via a register parameter
val valueIDt = value.inferType(program)
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val paramRegister = sub.asmParameterRegisters[parameter.index]
val paramRegister = if(registerOverride==null) sub.asmParameterRegisters[parameter.index] else RegisterOrStatusflag(registerOverride, null)
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val requiredDt = parameter.value.type
@@ -291,22 +312,21 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
is IdentifierReference -> {
val sourceName = asmgen.asmVariableName(value)
asmgen.out("""
pha
clc
lda $sourceName
beq +
sec
+ pla
""")
pha
clc
lda $sourceName
beq +
sec
+ pla""")
}
else -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out("""
beq +
sec
bcs ++
+ clc
+""")
beq +
sec
bcs ++
+ clc
+""")
}
}
} else throw AssemblyError("can only use Carry as status flag parameter")
@@ -1,4 +1,4 @@
package prog8.compiler.target.cpu6502.codegen
package prog8.codegen.target.cpu6502.codegen
import prog8.ast.Program
import prog8.ast.base.*
@@ -6,7 +6,7 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.PostIncrDecr
import prog8.ast.toHex
import prog8.compiler.target.AssemblyError
import prog8.codegen.target.AssemblyError
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
@@ -1,12 +1,12 @@
package prog8.compiler.target.cpu6502.codegen.assignment
package prog8.codegen.target.cpu6502.codegen.assignment
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compilerinterface.IMemSizer
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.codegen.target.AssemblyError
import prog8.codegen.target.cpu6502.codegen.AsmGen
internal enum class TargetStorageKind {
@@ -167,7 +167,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val dt = value.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
}
is FunctionCall -> {
is FunctionCallExpr -> {
when (val sub = value.target.targetStatement(program)) {
is Subroutine -> {
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first
@@ -175,7 +175,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
}
is BuiltinFunctionStatementPlaceholder -> {
is BuiltinFunctionPlaceholder -> {
val returnType = value.inferType(program)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
}
@@ -1,12 +1,12 @@
package prog8.compiler.target.cpu6502.codegen.assignment
package prog8.codegen.target.cpu6502.codegen.assignment
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.toHex
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.codegen.target.AssemblyError
import prog8.codegen.target.cpu6502.codegen.AsmGen
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType
import prog8.compilerinterface.builtinFunctionReturnType
@@ -159,7 +159,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
is ArrayIndexedExpression -> throw AssemblyError("source kind should have been array")
is DirectMemoryRead -> throw AssemblyError("source kind should have been memory")
is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, value)
is FunctionCall -> {
is FunctionCallExpr -> {
when (val sub = value.target.targetStatement(program)) {
is Subroutine -> {
asmgen.saveXbeforeCall(value)
@@ -215,7 +215,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
}
is BuiltinFunctionStatementPlaceholder -> {
is BuiltinFunctionPlaceholder -> {
val signature = BuiltinFunctions.getValue(sub.name)
asmgen.translateBuiltinFunctionCallExpression(value, signature, false, assign.target.register)
if(assign.target.register==null) {
@@ -271,6 +271,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
else -> throw AssemblyError("invalid prefix operator")
}
}
is ContainmentCheck -> {
containmentCheckIntoA(value)
assignRegisterByte(assign.target, CpuRegister.A)
}
else -> {
// Everything else just evaluate via the stack.
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
@@ -294,6 +298,124 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun containmentCheckIntoA(containment: ContainmentCheck) {
val elementDt = containment.element.inferType(program)
val range = containment.iterable as? RangeExpr
if(range!=null) {
val constRange = range.toConstantIntegerRange()
if(constRange!=null)
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), constRange.toList())
TODO("non-const range containment check ${containment.position}")
}
val variable = (containment.iterable as? IdentifierReference)?.targetVarDecl(program)
if(variable!=null) {
if(elementDt istype DataType.FLOAT)
throw AssemblyError("containment check of floats not supported")
if(variable.autogeneratedDontRemove) {
when(variable.datatype) {
DataType.STR -> {
require(elementDt.isBytes)
val stringVal = variable.value as StringLiteralValue
val encoded = program.encoding.encodeString(stringVal.value, stringVal.altEncoding)
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), encoded.map { it.toInt() })
}
DataType.ARRAY_F -> {
// require(elementDt istype DataType.FLOAT)
throw AssemblyError("containment check of floats not supported")
}
in ArrayDatatypes -> {
require(elementDt.isInteger)
val arrayVal = variable.value as ArrayLiteralValue
val values = arrayVal.value.map { it.constValue(program)!!.number.toInt() }
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), values)
}
else -> throw AssemblyError("invalid dt")
}
}
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
when(variable.datatype) {
DataType.STR -> {
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
val stringVal = variable.value as StringLiteralValue
asmgen.out(" ldy #${stringVal.value.length}")
asmgen.out(" jsr prog8_lib.containment_bytearray")
return
}
DataType.ARRAY_F -> throw AssemblyError("containment check of floats not supported")
DataType.ARRAY_B, DataType.ARRAY_UB -> {
val arrayVal = variable.value as ArrayLiteralValue
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
asmgen.out(" ldy #${arrayVal.value.size}")
asmgen.out(" jsr prog8_lib.containment_bytearray")
return
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
val arrayVal = variable.value as ArrayLiteralValue
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
asmgen.out(" ldy #${arrayVal.value.size}")
asmgen.out(" jsr prog8_lib.containment_wordarray")
return
}
else -> throw AssemblyError("invalid dt")
}
}
val stringVal = containment.iterable as? StringLiteralValue
if(stringVal!=null) {
require(elementDt.isBytes)
val encoded = program.encoding.encodeString(stringVal.value, stringVal.altEncoding)
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), encoded.map { it.toInt() })
}
val arrayVal = containment.iterable as? ArrayLiteralValue
if(arrayVal!=null) {
require(elementDt.isInteger)
val values = arrayVal.value.map { it.constValue(program)!!.number.toInt() }
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), values)
}
throw AssemblyError("invalid containment iterable type")
}
private fun containmentCheckIntoA(element: Expression, dt: DataType, values: List<Number>) {
if(values.size<2)
throw AssemblyError("containment check against 0 or 1 values should have been optimized away")
// TODO don't generate a huge cmp-list when we go over a certain number of values
val containsLabel = asmgen.makeLabel("contains")
when(dt) {
in ByteDatatypes -> {
asmgen.assignExpressionToRegister(element, RegisterOrPair.A, dt==DataType.BYTE)
for (value in values) {
asmgen.out(" cmp #$value | beq +")
}
asmgen.out("""
lda #0
beq ++
+ lda #1
+""")
}
in WordDatatypes -> {
asmgen.assignExpressionToRegister(element, RegisterOrPair.AY, dt==DataType.WORD)
for (value in values) {
asmgen.out("""
cmp #<$value
bne +
cpy #>$value
beq $containsLabel
+""")
}
asmgen.out("""
lda #0
beq +
$containsLabel lda #1
+""")
}
else -> throw AssemblyError("invalid dt")
}
}
private fun assignStatusFlagByte(target: AsmAssignTarget, statusflag: Statusflag) {
when(statusflag) {
Statusflag.Pc -> {
@@ -474,7 +596,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
val lsb = FunctionCallExpr(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
lsb.linkParents(value.parent)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb)
val assign = AsmAssignment(src, target, false, program.memsizer, value.position)
@@ -1,12 +1,12 @@
package prog8.compiler.target.cpu6502.codegen.assignment
package prog8.codegen.target.cpu6502.codegen.assignment
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.codegen.target.AssemblyError
import prog8.codegen.target.cpu6502.codegen.AsmGen
import prog8.compilerinterface.CpuType
@@ -45,7 +45,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
return inplaceModification(target, binExpr.operator, binExpr.right)
}
if (binExpr.operator in associativeOperators) {
if (binExpr.operator in AssociativeOperators) {
if (binExpr.right isSameAs astTarget) {
// A = 5 <operator> A
return inplaceModification(target, binExpr.operator, binExpr.left)
@@ -111,7 +111,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
if(leftBinExpr.right isSameAs astTarget) {
// X = (Left <oper> X) <oper> Something
if(leftBinExpr.operator in associativeOperators) {
if(leftBinExpr.operator in AssociativeOperators) {
inplaceModification(target, leftBinExpr.operator, leftBinExpr.left)
inplaceModification(target, binExpr.operator, binExpr.right)
return
@@ -123,7 +123,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
if(leftBinExpr==null && rightBinExpr!=null) {
if(rightBinExpr.left isSameAs astTarget) {
// X = Something <oper> (X <oper> Right)
if(binExpr.operator in associativeOperators) {
if(binExpr.operator in AssociativeOperators) {
inplaceModification(target, rightBinExpr.operator, rightBinExpr.right)
inplaceModification(target, binExpr.operator, binExpr.left)
return
@@ -133,7 +133,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
if(rightBinExpr.right isSameAs astTarget) {
// X = Something <oper> (Left <oper> X)
if(binExpr.operator in associativeOperators && rightBinExpr.operator in associativeOperators) {
if(binExpr.operator in AssociativeOperators && rightBinExpr.operator in AssociativeOperators) {
inplaceModification(target, rightBinExpr.operator, rightBinExpr.left)
inplaceModification(target, binExpr.operator, binExpr.left)
return
@@ -574,6 +574,26 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out(" eor $name | sta $name")
}
"==" -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out("""
cmp $name
beq +
lda #0
beq ++
+ lda #1
+ sta $name""")
}
"!=" -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out("""
cmp $name
beq +
lda #1
bne ++
+ lda #0
+ sta $name""")
}
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@@ -631,6 +651,26 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"&", "and" -> asmgen.out(" lda $name | and $otherName | sta $name")
"|", "or" -> asmgen.out(" lda $name | ora $otherName | sta $name")
"^", "xor" -> asmgen.out(" lda $name | eor $otherName | sta $name")
"==" -> {
asmgen.out("""
lda $otherName
cmp $name
beq +
lda #0
bne ++
+ lda #1
+ sta $name""")
}
"!=" -> {
asmgen.out("""
lda $otherName
cmp $name
beq +
lda #1
bne ++
+ lda #0
+ sta $name""")
}
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@@ -702,6 +742,26 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"&", "and" -> asmgen.out(" lda $name | and #$value | sta $name")
"|", "or" -> asmgen.out(" lda $name | ora #$value | sta $name")
"^", "xor" -> asmgen.out(" lda $name | eor #$value | sta $name")
"==" -> {
asmgen.out("""
lda #$value
cmp $name
beq +
lda #0
bne ++
+ lda #1
+ sta $name""")
}
"!=" -> {
asmgen.out("""
lda #$value
cmp $name
beq +
lda #1
bne ++
+ lda #0
+ sta $name""")
}
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@@ -1824,18 +1884,18 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
cmp #0
beq +
lda #1
+ eor #1""")
+ eor #1""")
RegisterOrPair.X -> asmgen.out("""
txa
beq +
lda #1
+ eor #1
+ eor #1
tax""")
RegisterOrPair.Y -> asmgen.out("""
tya
beq +
lda #1
+ eor #1
+ eor #1
tay""")
else -> throw AssemblyError("invalid reg dt for byte not")
}
@@ -1,8 +1,8 @@
package prog8.compiler.target.cx16
package prog8.codegen.target.cx16
import prog8.ast.base.DataType
import prog8.compiler.target.cbm.Mflpt5
import prog8.compiler.target.cbm.viceMonListPostfix
import prog8.codegen.target.cbm.Mflpt5
import prog8.codegen.target.cbm.viceMonListName
import prog8.compilerinterface.*
import java.io.IOException
import java.nio.file.Path
@@ -15,7 +15,6 @@ class CX16MachineDefinition: IMachineDefinition {
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val POINTER_MEM_SIZE = 2
override val BASIC_LOAD_ADDRESS = 0x0801u
override val RAW_LOAD_ADDRESS = 0x8000u
@@ -44,7 +43,7 @@ class CX16MachineDefinition: IMachineDefinition {
}
2 -> {
emulatorName = "box16"
extraArgs = listOf("-sym", "${programNameWithPath}.$viceMonListPostfix")
extraArgs = listOf("-sym", viceMonListName(programNameWithPath.toString()))
}
else -> {
System.err.println("Cx16 target only supports x16emu and box16 emulators.")
@@ -1,4 +1,4 @@
package prog8.compiler.target.cx16
package prog8.codegen.target.cx16
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.InternalCompilerException
@@ -8,7 +8,7 @@ import prog8.ast.base.FatalAstException
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.TypecastExpression
import prog8.ast.expressions.augmentAssignmentOperators
import prog8.ast.expressions.AugmentAssignmentOperators
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.walk.AstWalker
@@ -22,7 +22,7 @@ class BinExprSplitter(private val program: Program, private val options: Compila
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.value.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
if(assignment.value.inferType(program) istype DataType.FLOAT && !options.optimizeFloatExpressions)
return noModifications
val binExpr = assignment.value as? BinaryExpression
@@ -45,7 +45,7 @@ X = BinExpr X = LeftExpr
*/
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target)) {
if(binExpr.operator in AugmentAssignmentOperators + listOf("==", "!=") && isSimpleTarget(assignment.target)) {
if(assignment.target isSameAs binExpr.right)
return noModifications
if(assignment.target isSameAs binExpr.left) {
@@ -73,6 +73,15 @@ X = BinExpr X = LeftExpr
// )
}
if(binExpr.operator == "==" || binExpr.operator == "!=") {
// don't split if the operand(s) don't fit the type of the resulting variable
val targetDt = assignment.target.inferType(program)
val leftDt = binExpr.left.inferType(program)
val rightDt = binExpr.right.inferType(program)
if(leftDt isNotAssignableTo targetDt || rightDt isNotAssignableTo targetDt)
return noModifications
}
if(binExpr.right.isSimple) {
val firstAssign = Assignment(assignment.target.copy(), binExpr.left, binExpr.left.position)
val targetExpr = assignment.target.toExpression()
@@ -23,6 +23,13 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
noModifications
}
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
val result = containment.constValue(program)
if(result!=null)
return listOf(IAstModification.ReplaceNode(containment, result, parent))
return noModifications
}
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
// Try to turn a unary prefix expression into a single constant value.
// Compile-time constant sub expressions will be evaluated on the spot.
@@ -303,11 +310,11 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
return noModifications
}
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
override fun after(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
// the args of a fuction are constfolded via recursion already.
val constvalue = functionCall.constValue(program)
val constvalue = functionCallExpr.constValue(program)
return if(constvalue!=null)
listOf(IAstModification.ReplaceNode(functionCall, constvalue, parent))
listOf(IAstModification.ReplaceNode(functionCallExpr, constvalue, parent))
else
noModifications
}
@@ -425,7 +432,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
// both operators are the same.
// If associative, we can simply shuffle the const operands around to optimize.
if(expr.operator in associativeOperators) {
if(expr.operator in AssociativeOperators) {
return if(leftIsConst) {
if(subleftIsConst)
ShuffleOperands(expr, null, subExpr, subExpr.right, null, null, expr.left)
@@ -9,8 +9,6 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.size
import prog8.compilerinterface.toConstantIntegerRange
// Fix up the literal value's type to match that of the vardecl
// (also check range literal operands types before they get expanded into arrays for instance)
@@ -1,5 +1,6 @@
package prog8.optimizer
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
@@ -7,7 +8,10 @@ import prog8.ast.base.FatalAstException
import prog8.ast.base.IntegerDatatypes
import prog8.ast.base.NumericDatatypes
import prog8.ast.expressions.*
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.Assignment
import prog8.ast.statements.IfElse
import prog8.ast.statements.Jump
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import kotlin.math.abs
@@ -54,35 +58,26 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
return mods
}
override fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
if (expr.operator == "+") {
// +X --> X
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))
} else if (expr.operator == "not") {
when(expr.expression) {
is PrefixExpression -> {
// NOT(NOT(...)) -> ...
val pe = expr.expression as PrefixExpression
if(pe.operator == "not")
return listOf(IAstModification.ReplaceNode(expr, pe.expression, parent))
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
val truepart = ifElse.truepart
val elsepart = ifElse.elsepart
if(truepart.isNotEmpty() && elsepart.isNotEmpty()) {
if(truepart.statements.singleOrNull() is Jump) {
return listOf(
IAstModification.InsertAfter(ifElse, elsepart, parent as IStatementContainer),
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse)
)
}
if(elsepart.statements.singleOrNull() is Jump) {
val invertedCondition = invertCondition(ifElse.condition)
if(invertedCondition!=null) {
return listOf(
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
IAstModification.InsertAfter(ifElse, truepart, parent as IStatementContainer),
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse),
IAstModification.ReplaceNode(truepart, elsepart, ifElse)
)
}
is BinaryExpression -> {
// NOT (xxxx) -> invert the xxxx
val be = expr.expression as BinaryExpression
val newExpr = when (be.operator) {
"<" -> BinaryExpression(be.left, ">=", be.right, be.position)
">" -> BinaryExpression(be.left, "<=", be.right, be.position)
"<=" -> BinaryExpression(be.left, ">", be.right, be.position)
">=" -> BinaryExpression(be.left, "<", be.right, be.position)
"==" -> BinaryExpression(be.left, "!=", be.right, be.position)
"!=" -> BinaryExpression(be.left, "==", be.right, be.position)
else -> null
}
if (newExpr != null)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
else -> return noModifications
}
}
return noModifications
@@ -98,11 +93,11 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
throw FatalAstException("can't determine datatype of both expression operands $expr")
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
if (leftVal != null && expr.operator in associativeOperators && rightVal == null)
if (leftVal != null && expr.operator in AssociativeOperators && rightVal == null)
return listOf(IAstModification.SwapOperands(expr))
// NonBinaryExpression <associativeoperator> BinaryExpression --> BinaryExpression <associativeoperator> NonBinaryExpression
if (expr.operator in associativeOperators && expr.left !is BinaryExpression && expr.right is BinaryExpression) {
if (expr.operator in AssociativeOperators && expr.left !is BinaryExpression && expr.right is BinaryExpression) {
if(parent !is Assignment || !(expr.left isSameAs parent.target))
return listOf(IAstModification.SwapOperands(expr))
}
@@ -216,7 +211,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
// signedw < 0 --> msb(signedw) & $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
BinaryExpression(FunctionCallExpr(IdentifierReference(listOf("msb"), expr.position),
mutableListOf(expr.left),
expr.position
), "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
@@ -306,31 +301,31 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
return noModifications
}
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
if(functionCall.target.nameInSource == listOf("lsb")) {
val arg = functionCall.args[0]
override fun after(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
if(functionCallExpr.target.nameInSource == listOf("lsb")) {
val arg = functionCallExpr.args[0]
if(arg is TypecastExpression) {
val valueDt = arg.expression.inferType(program)
if (valueDt istype DataType.BYTE || valueDt istype DataType.UBYTE) {
// useless lsb() of byte value that was typecasted to word
return listOf(IAstModification.ReplaceNode(functionCall, arg.expression, parent))
return listOf(IAstModification.ReplaceNode(functionCallExpr, arg.expression, parent))
}
} else {
val argDt = arg.inferType(program)
if (argDt istype DataType.BYTE || argDt istype DataType.UBYTE) {
// useless lsb() of byte value
return listOf(IAstModification.ReplaceNode(functionCall, arg, parent))
return listOf(IAstModification.ReplaceNode(functionCallExpr, arg, parent))
}
}
}
else if(functionCall.target.nameInSource == listOf("msb")) {
val arg = functionCall.args[0]
else if(functionCallExpr.target.nameInSource == listOf("msb")) {
val arg = functionCallExpr.args[0]
if(arg is TypecastExpression) {
val valueDt = arg.expression.inferType(program)
if (valueDt istype DataType.BYTE || valueDt istype DataType.UBYTE) {
// useless msb() of byte value that was typecasted to word, replace with 0
return listOf(IAstModification.ReplaceNode(
functionCall,
functionCallExpr,
NumericLiteralValue(valueDt.getOr(DataType.UBYTE), 0.0, arg.expression.position),
parent))
}
@@ -339,7 +334,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
if (argDt istype DataType.BYTE || argDt istype DataType.UBYTE) {
// useless msb() of byte value, replace with 0
return listOf(IAstModification.ReplaceNode(
functionCall,
functionCallExpr,
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0.0, arg.position),
parent))
}
@@ -349,6 +344,25 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
return noModifications
}
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
val range = containment.iterable as? RangeExpr
if(range!=null && range.step.constValue(program)?.number==1.0) {
val from = range.from.constValue(program)
val to = range.to.constValue(program)
val value = containment.element
if(from!=null && to!=null && value.isSimple) {
if(to.number-from.number>6.0) {
// replace containment test with X>=from and X<=to
val left = BinaryExpression(value, ">=", from, containment.position)
val right = BinaryExpression(value.copy(), "<=", to, containment.position)
val comparison = BinaryExpression(left, "and", right, containment.position)
return listOf(IAstModification.ReplaceNode(containment, comparison, parent))
}
}
}
return noModifications
}
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
return when {
subBinExpr.left isSameAs x -> subBinExpr.right
@@ -460,7 +474,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
}
0.5 -> {
// sqrt(left)
return FunctionCall(IdentifierReference(listOf("sqrt"), expr.position), mutableListOf(expr.left), expr.position)
return FunctionCallExpr(IdentifierReference(listOf("sqrt"), expr.position), mutableListOf(expr.left), expr.position)
}
1.0 -> {
// left
@@ -653,12 +667,12 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
if (amount >= 16) {
return NumericLiteralValue(targetDt, 0.0, expr.position)
} else if (amount >= 8) {
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
val lsb = FunctionCallExpr(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
if (amount == 8) {
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
return FunctionCallExpr(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
}
val shifted = BinaryExpression(lsb, "<<", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
return FunctionCallExpr(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
}
}
else -> {
@@ -695,11 +709,11 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
return NumericLiteralValue.optimalInteger(0, expr.position)
}
else if (amount >= 8) {
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
val msb = FunctionCallExpr(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
if (amount == 8) {
// mkword(0, msb(v))
val zero = NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(zero, msb), expr.position)
return FunctionCallExpr(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)
}
@@ -717,7 +731,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
}
private fun reorderAssociativeWithConstant(expr: BinaryExpression, leftVal: NumericLiteralValue?): BinExprWithConstants {
if (expr.operator in associativeOperators && leftVal != null) {
if (expr.operator in AssociativeOperators && leftVal != null) {
// swap left and right so that right is always the constant
val tmp = expr.left
expr.left = expr.right
@@ -729,4 +743,4 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
private data class BinExprWithConstants(val expr: BinaryExpression, val leftVal: NumericLiteralValue?, val rightVal: NumericLiteralValue?)
}
}
@@ -72,10 +72,10 @@ fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICom
fun getTempVarName(dt: InferredTypes.InferredType): List<String> {
return when {
// TODO assume (hope) cx16.r9 isn't used for anything else during the use of this temporary variable...
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 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")
}
@@ -6,10 +6,8 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.size
import kotlin.math.floor
@@ -19,7 +17,7 @@ class StatementOptimizer(private val program: Program,
private val compTarget: ICompilationTarget
) : AstWalker() {
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
override fun before(functionCallExpr: FunctionCallExpr, 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.
@@ -28,7 +26,7 @@ class StatementOptimizer(private val program: Program,
return IdentifierReference(target.scopedName, variable.position)
}
val subroutine = functionCall.target.targetSubroutine(program)
val subroutine = functionCallExpr.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) {
@@ -48,7 +46,7 @@ class StatementOptimizer(private val program: Program,
is StringLiteralValue -> orig.copy()
else -> return noModifications
}
return listOf(IAstModification.ReplaceNode(functionCall, copy, parent))
return listOf(IAstModification.ReplaceNode(functionCallExpr, copy, parent))
}
}
return noModifications
@@ -57,7 +55,7 @@ class StatementOptimizer(private val program: Program,
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.targetStatement(program) is BuiltinFunctionStatementPlaceholder) {
if(functionCallStatement.target.targetStatement(program) is BuiltinFunctionPlaceholder) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in functions.purefunctionNames) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
@@ -134,33 +132,33 @@ class StatementOptimizer(private val program: Program,
return noModifications
}
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
// remove empty if statements
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isEmpty())
return listOf(IAstModification.Remove(ifStatement, parent as IStatementContainer))
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isEmpty())
return listOf(IAstModification.Remove(ifElse, parent as IStatementContainer))
// empty true part? switch with the else part
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isNotEmpty()) {
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isNotEmpty()) {
val invertedCondition = PrefixExpression("not", ifElse.condition, ifElse.condition.position)
val emptyscope = AnonymousScope(mutableListOf(), ifElse.elsepart.position)
val truepart = AnonymousScope(ifElse.elsepart.statements, ifElse.truepart.position)
return listOf(
IAstModification.ReplaceNode(ifStatement.condition, invertedCondition, ifStatement),
IAstModification.ReplaceNode(ifStatement.truepart, truepart, ifStatement),
IAstModification.ReplaceNode(ifStatement.elsepart, emptyscope, ifStatement)
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
IAstModification.ReplaceNode(ifElse.truepart, truepart, ifElse),
IAstModification.ReplaceNode(ifElse.elsepart, emptyscope, ifElse)
)
}
val constvalue = ifStatement.condition.constValue(program)
val constvalue = ifElse.condition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only if-part
errors.warn("condition is always true", ifStatement.position)
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.truepart, parent))
errors.warn("condition is always true", ifElse.condition.position)
listOf(IAstModification.ReplaceNode(ifElse, ifElse.truepart, parent))
} else {
// always false -> keep only else-part
errors.warn("condition is always false", ifStatement.position)
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.elsepart, parent))
errors.warn("condition is always false", ifElse.condition.position)
listOf(IAstModification.ReplaceNode(ifElse, ifElse.elsepart, parent))
}
}
@@ -228,15 +226,14 @@ class StatementOptimizer(private val program: Program,
override fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
val constvalue = untilLoop.condition.constValue(program)
if(constvalue!=null) {
if(constvalue.asBooleanValue) {
// always true -> keep only the statement block (if there are no break statements)
return if(constvalue.asBooleanValue) {
// always true -> keep only the statement block
errors.warn("condition is always true", untilLoop.condition.position)
if(!hasBreak(untilLoop.body))
return listOf(IAstModification.ReplaceNode(untilLoop, untilLoop.body, parent))
listOf(IAstModification.ReplaceNode(untilLoop, untilLoop.body, parent))
} else {
// always false
val forever = RepeatLoop(null, untilLoop.body, untilLoop.position)
return listOf(IAstModification.ReplaceNode(untilLoop, forever, parent))
listOf(IAstModification.ReplaceNode(untilLoop, forever, parent))
}
}
return noModifications
@@ -279,12 +276,25 @@ class StatementOptimizer(private val program: Program,
}
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
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))
// 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
}
override fun after(gosub: GoSub, parent: Node): Iterable<IAstModification> {
// if the next statement is return with no returnvalue, change into a regular jump if there are no parameters as well.
val subroutineParams = gosub.identifier?.targetSubroutine(program)?.parameters
if(subroutineParams!=null && subroutineParams.isEmpty()) {
val returnstmt = gosub.nextSibling() as? Return
if(returnstmt!=null && returnstmt.value==null) {
return listOf(
IAstModification.Remove(returnstmt, parent as IStatementContainer),
IAstModification.ReplaceNode(gosub, Jump(gosub.address, gosub.identifier, gosub.generatedLabel, gosub.position), parent)
)
}
}
return noModifications
}
@@ -299,7 +309,7 @@ class StatementOptimizer(private val program: Program,
val op1 = binExpr.operator
val op2 = rExpr.operator
if(rExpr.left is NumericLiteralValue && op2 in associativeOperators) {
if(rExpr.left is NumericLiteralValue && op2 in AssociativeOperators) {
// associative operator, make sure the constant numeric value is second (right)
return listOf(IAstModification.SwapOperands(rExpr))
}
@@ -335,7 +345,7 @@ class StatementOptimizer(private val program: Program,
}
}
if(binExpr.operator in associativeOperators && binExpr.right isSameAs assignment.target) {
if(binExpr.operator in AssociativeOperators && binExpr.right isSameAs assignment.target) {
// associative operator, swap the operands so that the assignment target is first (left)
// unless the other operand is the same in which case we don't swap (endless loop!)
if (!(binExpr.left isSameAs binExpr.right))
@@ -355,7 +365,7 @@ class StatementOptimizer(private val program: Program,
val targetIDt = assignment.target.inferType(program)
if(!targetIDt.isKnown)
throw FatalAstException("can't infer type of assignment target")
return noModifications
// optimize binary expressions a bit
val bexpr=assignment.value as? BinaryExpression
@@ -454,41 +464,12 @@ class StatementOptimizer(private val program: Program,
return null
}
when(returnStmt.value) {
is PrefixExpression -> {
val mod = returnViaIntermediaryVar(returnStmt.value!!)
if(mod!=null)
return mod
}
is BinaryExpression -> {
val mod = returnViaIntermediaryVar(returnStmt.value!!)
if(mod!=null)
return mod
}
else -> {}
if(returnStmt.value is BinaryExpression) {
val mod = returnViaIntermediaryVar(returnStmt.value!!)
if(mod!=null)
return mod
}
return super.after(returnStmt, parent)
return noModifications
}
private fun hasBreak(scope: IStatementContainer): Boolean {
class Searcher: IAstVisitor
{
var count=0
override fun visit(breakStmt: Break) {
count++
}
}
val s=Searcher()
for(stmt in scope.statements) {
stmt.accept(s)
if(s.count>0)
return true
}
return s.count > 0
}
}
@@ -33,8 +33,7 @@ class UnusedCodeRemover(private val program: Program,
}
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
if(!jump.isGosub)
reportUnreachable(jump)
reportUnreachable(jump)
return emptyList()
}
@@ -68,7 +67,8 @@ class UnusedCodeRemover(private val program: Program,
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
}
if(callgraph.unused(block)) {
errors.warn("removing unused block '${block.name}'", block.position)
if(block.statements.any{ it !is VarDecl || it.type==VarDeclType.VAR})
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
}
}
@@ -234,7 +234,7 @@ class UnusedCodeRemover(private val program: Program,
is PrefixExpression,
is BinaryExpression,
is TypecastExpression,
is FunctionCall -> { /* don't remove */ }
is FunctionCallExpr -> { /* don't remove */ }
else -> linesToRemove.add(assign1)
}
}
+683
View File
@@ -0,0 +1,683 @@
; --- low level floating point assembly routines for the C128
; these are almost all identical to the C64 except for a few details
; so we have to have a separate library file for the C128 unfortunately.
FL_ONE_const .byte 129 ; 1.0
FL_ZERO_const .byte 0,0,0,0,0 ; 0.0
FL_LOG2_const .byte $80, $31, $72, $17, $f8 ; log(2)
floats_store_reg .byte 0 ; temp storage
ub2float .proc
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy P8ZP_SCRATCH_B1
lda #0
jsr GIVAYF
_fac_to_mem ldx P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
jsr MOVMF
ldx P8ZP_SCRATCH_REG
rts
.pend
b2float .proc
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda P8ZP_SCRATCH_B1
jsr FREADSA
jmp ub2float._fac_to_mem
.pend
uw2float .proc
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr GIVUAYFAY
jmp ub2float._fac_to_mem
.pend
w2float .proc
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W1+1
jsr GIVAYF
jmp ub2float._fac_to_mem
.pend
cast_from_uw .proc
; -- uword in A/Y into float var at (P8ZP_SCRATCH_W2)
stx P8ZP_SCRATCH_REG
jsr GIVUAYFAY
jmp ub2float._fac_to_mem
.pend
cast_from_w .proc
; -- word in A/Y into float var at (P8ZP_SCRATCH_W2)
stx P8ZP_SCRATCH_REG
jsr GIVAYFAY
jmp ub2float._fac_to_mem
.pend
cast_from_ub .proc
; -- ubyte in Y into float var at (P8ZP_SCRATCH_W2)
stx P8ZP_SCRATCH_REG
jsr FREADUY
jmp ub2float._fac_to_mem
.pend
cast_from_b .proc
; -- byte in A into float var at (P8ZP_SCRATCH_W2)
stx P8ZP_SCRATCH_REG
jsr FREADSA
jmp ub2float._fac_to_mem
.pend
cast_as_uw_into_ya .proc ; also used for float 2 ub
; -- cast float at A/Y to uword into Y/A
jsr MOVFM
jmp cast_FAC1_as_uw_into_ya
.pend
cast_as_w_into_ay .proc ; also used for float 2 b
; -- cast float at A/Y to word into A/Y
jsr MOVFM
jmp cast_FAC1_as_w_into_ay
.pend
cast_FAC1_as_uw_into_ya .proc ; also used for float 2 ub
; -- cast fac1 to uword into Y/A
stx P8ZP_SCRATCH_REG
jsr GETADR ; into Y/A
ldx P8ZP_SCRATCH_REG
rts
.pend
cast_FAC1_as_w_into_ay .proc ; also used for float 2 b
; -- cast fac1 to word into A/Y
stx P8ZP_SCRATCH_REG
jsr AYINT
ldy $66
lda $67
ldx P8ZP_SCRATCH_REG
rts
.pend
stack_b2float .proc
; -- b2float operating on the stack
inx
lda P8ESTACK_LO,x
stx P8ZP_SCRATCH_REG
jsr FREADSA
jmp push_fac1._internal
.pend
stack_w2float .proc
; -- w2float operating on the stack
inx
ldy P8ESTACK_LO,x
lda P8ESTACK_HI,x
stx P8ZP_SCRATCH_REG
jsr GIVAYF
jmp push_fac1._internal
.pend
stack_ub2float .proc
; -- ub2float operating on the stack
inx
lda P8ESTACK_LO,x
stx P8ZP_SCRATCH_REG
tay
lda #0
jsr GIVAYF
jmp push_fac1._internal
.pend
stack_uw2float .proc
; -- uw2float operating on the stack
inx
lda P8ESTACK_LO,x
ldy P8ESTACK_HI,x
stx P8ZP_SCRATCH_REG
jsr GIVUAYFAY
jmp push_fac1._internal
.pend
stack_float2w .proc ; also used for float2b
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr AYINT
ldx P8ZP_SCRATCH_REG
lda $66
sta P8ESTACK_HI,x
lda $67
sta P8ESTACK_LO,x
dex
rts
.pend
stack_float2uw .proc ; also used for float2ub
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr GETADR
ldx P8ZP_SCRATCH_REG
sta P8ESTACK_HI,x
tya
sta P8ESTACK_LO,x
dex
rts
.pend
push_float .proc
; ---- push mflpt5 in A/Y onto stack
; (taking 3 stack positions = 6 bytes of which 1 is padding)
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
sta P8ESTACK_LO,x
iny
lda (P8ZP_SCRATCH_W1),y
sta P8ESTACK_HI,x
dex
iny
lda (P8ZP_SCRATCH_W1),y
sta P8ESTACK_LO,x
iny
lda (P8ZP_SCRATCH_W1),y
sta P8ESTACK_HI,x
dex
iny
lda (P8ZP_SCRATCH_W1),y
sta P8ESTACK_LO,x
dex
rts
.pend
pop_float .proc
; ---- pops mflpt5 from stack to memory A/Y
; (frees 3 stack positions = 6 bytes of which 1 is padding)
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #4
inx
lda P8ESTACK_LO,x
sta (P8ZP_SCRATCH_W1),y
dey
inx
lda P8ESTACK_HI,x
sta (P8ZP_SCRATCH_W1),y
dey
lda P8ESTACK_LO,x
sta (P8ZP_SCRATCH_W1),y
dey
inx
lda P8ESTACK_HI,x
sta (P8ZP_SCRATCH_W1),y
dey
lda P8ESTACK_LO,x
sta (P8ZP_SCRATCH_W1),y
rts
.pend
pop_float_fac1 .proc
; -- pops float from stack into FAC1
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jmp MOVFM
.pend
copy_float .proc
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
sta _target+1
sty _target+2
ldy #4
_loop lda (P8ZP_SCRATCH_W1),y
_target sta $ffff,y ; modified
dey
bpl _loop
rts
.pend
inc_var_f .proc
; -- add 1 to float pointed to by A/Y
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
stx P8ZP_SCRATCH_REG
jsr MOVFM
lda #<FL_ONE_const
ldy #>FL_ONE_const
jsr FADD
ldx P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr MOVMF
ldx P8ZP_SCRATCH_REG
rts
.pend
dec_var_f .proc
; -- subtract 1 from float pointed to by A/Y
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
stx P8ZP_SCRATCH_REG
lda #<FL_ONE_const
ldy #>FL_ONE_const
jsr MOVFM
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr FSUB
ldx P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr MOVMF
ldx P8ZP_SCRATCH_REG
rts
.pend
pop_2_floats_f2_in_fac1 .proc
; -- pop 2 floats from stack, load the second one in FAC1 as well
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float2
ldy #>fmath_float2
jmp MOVFM
.pend
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
push_fac1 .proc
; -- push the float in FAC1 onto the stack
stx P8ZP_SCRATCH_REG
_internal ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
lda #<fmath_float1
ldy #>fmath_float1
ldx P8ZP_SCRATCH_REG
jmp push_float
.pend
pow_f .proc
; -- push f1 ** f2 on stack
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
stx P8ZP_SCRATCH_REG
lda #<fmath_float1
ldy #>fmath_float1
jsr ROMUPK ; fac2 = float1
lda #<fmath_float2
ldy #>fmath_float2
jsr FPWR
jmp push_fac1._internal
.pend
div_f .proc
; -- push f1/f2 on stack
jsr pop_2_floats_f2_in_fac1
stx P8ZP_SCRATCH_REG
lda #<fmath_float1
ldy #>fmath_float1
jsr FDIV
jmp push_fac1._internal
.pend
add_f .proc
; -- push f1+f2 on stack
jsr pop_2_floats_f2_in_fac1
stx P8ZP_SCRATCH_REG
lda #<fmath_float1
ldy #>fmath_float1
jsr FADD
jmp push_fac1._internal
.pend
sub_f .proc
; -- push f1-f2 on stack
jsr pop_2_floats_f2_in_fac1
stx P8ZP_SCRATCH_REG
lda #<fmath_float1
ldy #>fmath_float1
jsr FSUB
jmp push_fac1._internal
.pend
mul_f .proc
; -- push f1*f2 on stack
jsr pop_2_floats_f2_in_fac1
stx P8ZP_SCRATCH_REG
lda #<fmath_float1
ldy #>fmath_float1
jsr FMULT
jmp push_fac1._internal
.pend
neg_f .proc
; -- toggle the sign bit on the stack
lda P8ESTACK_HI+3,x
eor #$80
sta P8ESTACK_HI+3,x
rts
.pend
var_fac1_less_f .proc
; -- is the float in FAC1 < the variable AY?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #255
beq +
lda #0
rts
+ lda #1
rts
.pend
var_fac1_lesseq_f .proc
; -- is the float in FAC1 <= the variable AY?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #0
beq +
cmp #255
beq +
lda #0
rts
+ lda #1
rts
.pend
var_fac1_greater_f .proc
; -- is the float in FAC1 > the variable AY?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #1
beq +
lda #0
rts
+ lda #1
rts
.pend
var_fac1_greatereq_f .proc
; -- is the float in FAC1 >= the variable AY?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #0
beq +
cmp #1
beq +
lda #0
rts
+ lda #1
rts
.pend
var_fac1_notequal_f .proc
; -- are the floats numbers in FAC1 and the variable AY *not* identical?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
and #1
rts
.pend
vars_equal_f .proc
; -- are the mflpt5 numbers in P8ZP_SCRATCH_W1 and AY identical?
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bne _false
iny
lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bne _false
iny
lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bne _false
iny
lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bne _false
iny
lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bne _false
lda #1
rts
_false lda #0
rts
.pend
equal_f .proc
; -- are the two mflpt5 numbers on the stack identical?
inx
inx
inx
inx
lda P8ESTACK_LO-3,x
cmp P8ESTACK_LO,x
bne _equals_false
lda P8ESTACK_LO-2,x
cmp P8ESTACK_LO+1,x
bne _equals_false
lda P8ESTACK_LO-1,x
cmp P8ESTACK_LO+2,x
bne _equals_false
lda P8ESTACK_HI-2,x
cmp P8ESTACK_HI+1,x
bne _equals_false
lda P8ESTACK_HI-1,x
cmp P8ESTACK_HI+2,x
bne _equals_false
_equals_true lda #1
_equals_store inx
sta P8ESTACK_LO+1,x
rts
_equals_false lda #0
beq _equals_store
.pend
notequal_f .proc
; -- are the two mflpt5 numbers on the stack different?
jsr equal_f
eor #1 ; invert the result
sta P8ESTACK_LO+1,x
rts
.pend
vars_less_f .proc
; -- is float in AY < float in P8ZP_SCRATCH_W2 ?
jsr MOVFM
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #255
bne +
lda #1
rts
+ lda #0
rts
.pend
vars_lesseq_f .proc
; -- is float in AY <= float in P8ZP_SCRATCH_W2 ?
jsr MOVFM
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #255
bne +
- lda #1
rts
+ cmp #0
beq -
lda #0
rts
.pend
less_f .proc
; -- is f1 < f2?
jsr compare_floats
cmp #255
beq compare_floats._return_true
bne compare_floats._return_false
.pend
lesseq_f .proc
; -- is f1 <= f2?
jsr compare_floats
cmp #255
beq compare_floats._return_true
cmp #0
beq compare_floats._return_true
bne compare_floats._return_false
.pend
greater_f .proc
; -- is f1 > f2?
jsr compare_floats
cmp #1
beq compare_floats._return_true
bne compare_floats._return_false
.pend
greatereq_f .proc
; -- is f1 >= f2?
jsr compare_floats
cmp #1
beq compare_floats._return_true
cmp #0
beq compare_floats._return_true
bne compare_floats._return_false
.pend
compare_floats .proc
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr MOVFM ; fac1 = flt1
lda #<fmath_float2
ldy #>fmath_float2
stx P8ZP_SCRATCH_REG
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
ldx P8ZP_SCRATCH_REG
rts
_return_false lda #0
_return_result sta P8ESTACK_LO,x
dex
rts
_return_true lda #1
bne _return_result
.pend
set_array_float_from_fac1 .proc
; -- set the float in FAC1 in the array (index in A, array in P8ZP_SCRATCH_W1)
sta P8ZP_SCRATCH_B1
asl a
asl a
clc
adc P8ZP_SCRATCH_B1
ldy P8ZP_SCRATCH_W1+1
clc
adc P8ZP_SCRATCH_W1
bcc +
iny
+ stx floats_store_reg
tax
jsr MOVMF
ldx floats_store_reg
rts
.pend
set_0_array_float .proc
; -- set a float in an array to zero (index in A, array in P8ZP_SCRATCH_W1)
sta P8ZP_SCRATCH_B1
asl a
asl a
clc
adc P8ZP_SCRATCH_B1
tay
lda #0
sta (P8ZP_SCRATCH_W1),y
iny
sta (P8ZP_SCRATCH_W1),y
iny
sta (P8ZP_SCRATCH_W1),y
iny
sta (P8ZP_SCRATCH_W1),y
iny
sta (P8ZP_SCRATCH_W1),y
rts
.pend
set_array_float .proc
; -- set a float in an array to a value (index in A, float in P8ZP_SCRATCH_W1, array in P8ZP_SCRATCH_W2)
sta P8ZP_SCRATCH_B1
asl a
asl a
clc
adc P8ZP_SCRATCH_B1
adc P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
bcc +
iny
+ jmp copy_float
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
.pend
+159
View File
@@ -0,0 +1,159 @@
; Prog8 definitions for floating point handling on the Commodore 128
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
%option enable_floats
floats {
; ---- this block contains C-128 compatible floating point related functions ----
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
float tempvar_swap_float ; used for some swap() operations
; ---- ROM float functions ----
; note: the fac1 and fac2 are working registers and take 6 bytes each,
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
romsub $af00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 102-103 ($66-$67) MSB FIRST. (might throw ILLEGAL QUANTITY)
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
; (tip: use GIVAYFAY to use A/Y input; lo/hi switched to normal order)
romsub $af03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
romsub $af09 = VAL(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $24/25 must point to string in bank1, A=string length Don't call this from bank0
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $16/17)
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
romsub $af0c = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
romsub $af12 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
romsub $af15 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
romsub $af18 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y in bank 1
romsub $af1b = FADDT() clobbers(A,X,Y) ; fac1 += fac2
romsub $af2a = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
romsub $af1e = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
romsub $af21 = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
romsub $af5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory bank 1 in A/Y into fac2
romsub $af5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
romsub $af24 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
romsub $af27 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
romsub $af63 = MOVFM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1
romsub $af60 = MOVFRM() clobbers(A,X,Y) ; load mflpt value from memory in $24/$25 into fac2
romsub $af66 = MOVMF(uword mflpt @ XY) clobbers(A,X,Y) ; store fac1 to memory X/Y as 5-byte mflpt
romsub $af69 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $af6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2
romsub $af6c = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $af51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $af4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
romsub $af54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $af2d = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
romsub $af06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY ($0100)
romsub $af30 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $af36 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
romsub $af39 = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
romsub $af33 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
romsub $af3c = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $af57 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $af3f = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $af42 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
romsub $af45 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
romsub $af48 = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
; ---- 8 bit signed A -> float in fac1
%asm {{
tay
bpl +
lda #$ff
jmp GIVAYF
+ lda #0
jmp GIVAYF
}}
}
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
%asm {{
stx P8ZP_SCRATCH_REG
sta _tmp
sty P8ZP_SCRATCH_B1
tya
ldy _tmp
jsr GIVAYF ; load it as signed... correct afterwards
lda P8ZP_SCRATCH_B1
bpl +
lda #<_flt65536
ldy #>_flt65536
jsr ROMUPK
jsr FADDT
+ ldx P8ZP_SCRATCH_REG
rts
_tmp .byte 0
_flt65536 .byte 145,0,0,0,0 ; 65536.0
}}
}
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
%asm {{
sta P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_B1
jmp GIVAYF ; this uses the inverse order, Y/A
}}
}
asmsub GETADRAY () clobbers(X) -> uword @ AY {
; ---- fac1 to unsigned word in A/Y
%asm {{
jsr GETADR ; this uses the inverse order, Y/A
sta P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_B1
rts
}}
}
asmsub FREADUY (ubyte value @Y) {
; -- 8 bit unsigned Y -> float in fac1
%asm {{
lda #0
jmp GIVAYF
}}
}
sub print_f (float value) {
; ---- prints the floating point value (without a newline).
%asm {{
stx P8ZP_SCRATCH_REG
lda #<value
ldy #>value
jsr MOVFM ; load float into fac1
jsr FOUT ; fac1 to string in A/Y
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq +
jsr c64.CHROUT
iny
bne -
+ ldx P8ZP_SCRATCH_REG
rts
}}
}
%asminclude "library:c128/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
}
+60
View File
@@ -0,0 +1,60 @@
%import syslib
%import textio
; Bitmap pixel graphics module for the Commodore 128
; TODO c128 actually implement the graphics routines. Ideally a way to 'borrow' the code form the C64 version without just copy-pasting that here?
graphics {
const uword WIDTH = 320
const ubyte HEIGHT = 200
sub enable_bitmap_mode() {
; enable bitmap screen, erase it and set colors to black/white.
; TODO
}
sub disable_bitmap_mode() {
; enables text mode, erase the text screen, color white
; TODO
}
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
; TODO
}
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
; TODO
}
sub fillrect(uword x, uword y, uword width, uword height) {
; TODO
}
sub rect(uword x, uword y, uword width, uword height) {
; TODO
}
sub horizontal_line(uword x, uword y, uword length) {
; TODO
}
sub vertical_line(uword x, uword y, uword height) {
; TODO
}
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
; TODO
}
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; TODO
}
inline asmsub plot(uword plotx @R0, uword ploty @R1) clobbers(A, X, Y) {
%asm {{
nop ; TODO
}}
}
}
+796
View File
@@ -0,0 +1,796 @@
; Prog8 definitions for the Commodore-128
; Including memory registers, I/O registers, Basic and Kernal subroutines.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
c64 {
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STATUS = $90 ; kernal status variable for I/O
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
;;&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ) // TODO c128 ??
&ubyte COLOR = $00f1 ; cursor color
;;&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address) // TODO c128 ??
&uword CINV = $0314 ; IRQ vector (in ram)
&uword CBINV = $0316 ; BRK vector (in ram)
&uword NMINV = $0318 ; NMI vector (in ram)
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
; the default addresses for the character screen chars and colors
const uword Screen = $0400 ; to have this as an array[40*25] the compiler would have to support array size > 255
const uword Colors = $d800 ; to have this as an array[40*25] the compiler would have to support array size > 255
; the default locations of the 8 sprite pointers (store address of sprite / 64)
&ubyte SPRPTR0 = 2040
&ubyte SPRPTR1 = 2041
&ubyte SPRPTR2 = 2042
&ubyte SPRPTR3 = 2043
&ubyte SPRPTR4 = 2044
&ubyte SPRPTR5 = 2045
&ubyte SPRPTR6 = 2046
&ubyte SPRPTR7 = 2047
&ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array.
; ---- VIC-II 6567/6569/856x registers ----
&ubyte SP0X = $d000
&ubyte SP0Y = $d001
&ubyte SP1X = $d002
&ubyte SP1Y = $d003
&ubyte SP2X = $d004
&ubyte SP2Y = $d005
&ubyte SP3X = $d006
&ubyte SP3Y = $d007
&ubyte SP4X = $d008
&ubyte SP4Y = $d009
&ubyte SP5X = $d00a
&ubyte SP5Y = $d00b
&ubyte SP6X = $d00c
&ubyte SP6Y = $d00d
&ubyte SP7X = $d00e
&ubyte SP7Y = $d00f
&ubyte[16] SPXY = $d000 ; the 8 sprite X and Y registers as an array.
&uword[8] SPXYW = $d000 ; the 8 sprite X and Y registers as a combined xy word array.
&ubyte MSIGX = $d010
&ubyte SCROLY = $d011
&ubyte RASTER = $d012
&ubyte LPENX = $d013
&ubyte LPENY = $d014
&ubyte SPENA = $d015
&ubyte SCROLX = $d016
&ubyte YXPAND = $d017
&ubyte VMCSB = $d018
&ubyte VICIRQ = $d019
&ubyte IREQMASK = $d01a
&ubyte SPBGPR = $d01b
&ubyte SPMC = $d01c
&ubyte XXPAND = $d01d
&ubyte SPSPCL = $d01e
&ubyte SPBGCL = $d01f
&ubyte EXTCOL = $d020 ; border color
&ubyte BGCOL0 = $d021 ; screen color
&ubyte BGCOL1 = $d022
&ubyte BGCOL2 = $d023
&ubyte BGCOL4 = $d024
&ubyte SPMC0 = $d025
&ubyte SPMC1 = $d026
&ubyte SP0COL = $d027
&ubyte SP1COL = $d028
&ubyte SP2COL = $d029
&ubyte SP3COL = $d02a
&ubyte SP4COL = $d02b
&ubyte SP5COL = $d02c
&ubyte SP6COL = $d02d
&ubyte SP7COL = $d02e
&ubyte[8] SPCOL = $d027
; ---- end of VIC-II registers ----
; ---- CIA 6526 1 & 2 registers ----
&ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive (and joystick control port #2)
&ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port (and joystick control port #1)
&ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
&ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
&ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte
&ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte
&ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte
&ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte
&ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
&ubyte CIA1TODSEC = $DC09 ; time of day, seconds
&ubyte CIA1TODMMIN = $DC0A ; time of day, minutes
&ubyte CIA1TODHR = $DC0B ; time of day, hours
&ubyte CIA1SDR = $DC0C ; Serial Data Register
&ubyte CIA1ICR = $DC0D
&ubyte CIA1CRA = $DC0E
&ubyte CIA1CRB = $DC0F
&ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
&ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
&ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
&ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
&ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte
&ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte
&ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte
&ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte
&ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
&ubyte CIA2TODSEC = $DD09 ; time of day, seconds
&ubyte CIA2TODMIN = $DD0A ; time of day, minutes
&ubyte CIA2TODHR = $DD0B ; time of day, hours
&ubyte CIA2SDR = $DD0C ; Serial Data Register
&ubyte CIA2ICR = $DD0D
&ubyte CIA2CRA = $DD0E
&ubyte CIA2CRB = $DD0F
; ---- end of CIA registers ----
; ---- SID 6581/8580 registers ----
&ubyte FREQLO1 = $D400 ; channel 1 freq lo
&ubyte FREQHI1 = $D401 ; channel 1 freq hi
&uword FREQ1 = $D400 ; channel 1 freq (word)
&ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0)
&ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8)
&uword PW1 = $D402 ; channel 1 pulse width (word)
&ubyte CR1 = $D404 ; channel 1 voice control register
&ubyte AD1 = $D405 ; channel 1 attack & decay
&ubyte SR1 = $D406 ; channel 1 sustain & release
&ubyte FREQLO2 = $D407 ; channel 2 freq lo
&ubyte FREQHI2 = $D408 ; channel 2 freq hi
&uword FREQ2 = $D407 ; channel 2 freq (word)
&ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0)
&ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8)
&uword PW2 = $D409 ; channel 2 pulse width (word)
&ubyte CR2 = $D40B ; channel 2 voice control register
&ubyte AD2 = $D40C ; channel 2 attack & decay
&ubyte SR2 = $D40D ; channel 2 sustain & release
&ubyte FREQLO3 = $D40E ; channel 3 freq lo
&ubyte FREQHI3 = $D40F ; channel 3 freq hi
&uword FREQ3 = $D40E ; channel 3 freq (word)
&ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0)
&ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8)
&uword PW3 = $D410 ; channel 3 pulse width (word)
&ubyte CR3 = $D412 ; channel 3 voice control register
&ubyte AD3 = $D413 ; channel 3 attack & decay
&ubyte SR3 = $D414 ; channel 3 sustain & release
&ubyte FCLO = $D415 ; filter cutoff lo (2-0)
&ubyte FCHI = $D416 ; filter cutoff hi (10-3)
&uword FC = $D415 ; filter cutoff (word)
&ubyte RESFILT = $D417 ; filter resonance and routing
&ubyte MVOL = $D418 ; filter mode and main volume control
&ubyte POTX = $D419 ; potentiometer X
&ubyte POTY = $D41A ; potentiometer Y
&ubyte OSC3 = $D41B ; channel 3 oscillator value read
&ubyte ENV3 = $D41C ; channel 3 envelope value read
; ---- end of SID registers ----
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
; STROUT --> use txt.print
; CLEARSCR -> use txt.clear_screen
; HOMECRSR -> use txt.home or txt.plot
romsub $FA65 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
romsub $FF33 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
; TODO c128 a bunch of kernal routines are missing here that are specific to the c128
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
; ---- end of C64 compatible ROM kernal routines ----
; ---- utilities -----
asmsub STOP2() -> ubyte @A {
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
%asm {{
txa
pha
jsr c64.STOP
beq +
pla
tax
lda #0
rts
+ pla
tax
lda #1
rts
}}
}
asmsub RDTIM16() -> uword @AY {
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
%asm {{
stx P8ZP_SCRATCH_REG
jsr c64.RDTIM
pha
txa
tay
pla
ldx P8ZP_SCRATCH_REG
rts
}}
}
; ---- system utility routines that are essentially the same as on the C64: -----
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
%asm {{
lda #$80
sta 247 ; disable charset switching
lda #112
sta 808 ; disable run/stop key
rts
}}
}
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
adc #0
sta _use_kernal
sei
lda #<_irq_handler
sta c64.CINV
lda #>_irq_handler
sta c64.CINV+1
cli
rts
_irq_handler jsr _irq_handler_init
_modified jsr $ffff ; modified
jsr _irq_handler_end
lda _use_kernal
bne +
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp c64.IRQDFRT ; continue with normal kernal irq routine
_use_kernal .byte 0
_irq_handler_init
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
stx IRQ_X_REG
lda P8ZP_SCRATCH_B1
sta IRQ_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta IRQ_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta IRQ_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta IRQ_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta IRQ_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta IRQ_SCRATCH_ZPWORD2+1
; stack protector; make sure we don't clobber the top of the evaluation stack
dex
dex
dex
dex
dex
dex
cld
rts
_irq_handler_end
; restore all zp scratch registers and the X register
lda IRQ_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda IRQ_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda IRQ_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda IRQ_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda IRQ_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda IRQ_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
ldx IRQ_X_REG
rts
IRQ_X_REG .byte 0
IRQ_SCRATCH_ZPB1 .byte 0
IRQ_SCRATCH_ZPREG .byte 0
IRQ_SCRATCH_ZPWORD1 .word 0
IRQ_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_irq() clobbers(A) {
%asm {{
sei
lda #<c64.IRQDFRT
sta c64.CINV
lda #>c64.IRQDFRT
sta c64.CINV+1
lda #0
sta c64.IREQMASK ; disable raster irq
lda #%10000001
sta c64.CIA1ICR ; restore CIA1 irq
cli
rts
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
adc #0
sta set_irq._use_kernal
lda cx16.r0
ldy cx16.r0+1
sei
jsr _setup_raster_irq
lda #<_raster_irq_handler
sta c64.CINV
lda #>_raster_irq_handler
sta c64.CINV+1
cli
rts
_raster_irq_handler
jsr set_irq._irq_handler_init
_modified jsr $ffff ; modified
jsr set_irq._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda set_irq._use_kernal
bne +
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp c64.IRQDFRT ; continue with kernal irq routine
_setup_raster_irq
pha
lda #%01111111
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
and c64.SCROLY
sta c64.SCROLY ; clear most significant bit of raster position
lda c64.CIA1ICR ; ack previous irq
lda c64.CIA2ICR ; ack previous irq
pla
sta c64.RASTER ; set the raster line number where interrupt should occur
cpy #0
beq +
lda c64.SCROLY
ora #%10000000
sta c64.SCROLY ; set most significant bit of raster position
+ lda #%00000001
sta c64.IREQMASK ;enable raster interrupt signals from vic
rts
}}
}
}
c128 {
; ---- C128 specific registers ----
&ubyte VM1 = $0A2C ; shadow for VUC $d018 in text mode
&ubyte VM2 = $0A2D ; shadow for VIC $d018 in bitmap screen mode
&ubyte VM3 = $0A2E ; starting page for VDC screen mem
&ubyte VM4 = $0A2F ; starting page for VDC attribute mem
; ---- C128 specific system utility routines: ----
asmsub init_system() {
; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic.
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
; Also a different color scheme is chosen to identify ourselves a little.
; Uppercase charset is activated, and all three registers set to 0, status flags cleared.
%asm {{
sei
cld
;;lda #%00101111 ; TODO c128 ram and rom bank selection how?
;;sta $00
;;lda #%00100111
;;sta $01
jsr c64.IOINIT
jsr c64.RESTOR
jsr c64.CINT
lda #6
sta c64.EXTCOL
lda #7
sta c64.COLOR
lda #0
sta c64.BGCOL0
jsr c64.disable_runstop_and_charsetswitch
clc
clv
cli
rts
}}
}
asmsub init_system_phase2() {
%asm {{
rts ; no phase 2 steps on the C128
}}
}
asmsub disable_basic() clobbers(A) {
%asm {{
lda $0a04 ; disable BASIC shadow registers
and #$fe
sta $0a04
lda #$01 ; disable BASIC IRQ service routine
sta $12fd
lda #$ff ; disable screen editor IRQ setup
sta $d8
lda #$b7 ; skip programmable function key check
sta $033c
lda #$0e ; bank out BASIC ROM
sta $ff00
rts
}}
}
; ---- end of C128 specific system utility routines ----
}
sys {
; ------- lowlevel system routines --------
const ubyte target = 128 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
asmsub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
%asm {{
sei
;lda #14
;sta $01 ; bank the kernal in TODO c128 how to do this?
jmp (c64.RESET_VEC)
}}
}
sub wait(uword jiffies) {
; --- wait approximately the given number of jiffies (1/60th seconds)
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
repeat jiffies {
ubyte jiff = lsb(c64.RDTIM16())
while jiff==lsb(c64.RDTIM16()) {
; wait until 1 jiffy has passed
}
}
}
asmsub waitvsync() clobbers(A) {
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
; note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
%asm {{
- bit c64.SCROLY
bpl -
- bit c64.SCROLY
bmi -
rts
}}
}
inline asmsub waitrastborder() {
; --- busy wait till the raster position has reached the bottom screen border (approximately)
; note: a more accurate way to do this is by using a raster irq handler instead.
%asm {{
- bit c64.SCROLY
bpl -
}}
}
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
%asm {{
ldx cx16.r0
stx P8ZP_SCRATCH_W1 ; source in ZP
ldx cx16.r0+1
stx P8ZP_SCRATCH_W1+1
ldx cx16.r1
stx P8ZP_SCRATCH_W2 ; target in ZP
ldx cx16.r1+1
stx P8ZP_SCRATCH_W2+1
cpy #0
bne _longcopy
; copy <= 255 bytes
tay
bne _copyshort
rts ; nothing to copy
_copyshort
; decrease source and target pointers so we can simply index by Y
lda P8ZP_SCRATCH_W1
bne +
dec P8ZP_SCRATCH_W1+1
+ dec P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W2
bne +
dec P8ZP_SCRATCH_W2+1
+ dec P8ZP_SCRATCH_W2
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
dey
bne -
rts
_longcopy
sta P8ZP_SCRATCH_B1 ; lsb(count) = remainder in last page
tya
tax ; x = num pages (1+)
ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
bne -
inc P8ZP_SCRATCH_W1+1
inc P8ZP_SCRATCH_W2+1
dex
bne -
ldy P8ZP_SCRATCH_B1
bne _copyshort
rts
}}
}
asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
%asm {{
ldy cx16.r0
sty P8ZP_SCRATCH_W1
ldy cx16.r0+1
sty P8ZP_SCRATCH_W1+1
ldx cx16.r1
ldy cx16.r1+1
jmp prog8_lib.memset
}}
}
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers(A,X,Y) {
%asm {{
ldx cx16.r0
stx P8ZP_SCRATCH_W1
ldx cx16.r0+1
stx P8ZP_SCRATCH_W1+1
ldx cx16.r1
stx P8ZP_SCRATCH_W2
ldx cx16.r1+1
stx P8ZP_SCRATCH_W2+1
jmp prog8_lib.memsetw
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
pla
}}
}
inline asmsub clear_carry() {
%asm {{
clc
}}
}
inline asmsub set_carry() {
%asm {{
sec
}}
}
inline asmsub clear_irqd() {
%asm {{
cli
}}
}
inline asmsub set_irqd() {
%asm {{
sei
}}
}
inline asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
jsr c64.CLRCHN ; reset i/o channels
ldx prog8_lib.orig_stackpointer
txs
rts ; return to original caller
}}
}
inline asmsub progend() -> uword @AY {
%asm {{
lda #<prog8_program_end
ldy #>prog8_program_end
}}
}
}
cx16 {
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
; they are simulated on the C128 as well but their location in memory is different
; (because there's no room for them in the zeropage)
; $1300-$1bff is unused RAM on C128. We'll use $1a00-$1bff as the lo/hi evalstack.
; the virtual registers are allocated at the bottom of the eval-stack (should be ample space unless
; you're doing insane nesting of expressions...)
&uword r0 = $1b00
&uword r1 = $1b02
&uword r2 = $1b04
&uword r3 = $1b06
&uword r4 = $1b08
&uword r5 = $1b0a
&uword r6 = $1b0c
&uword r7 = $1b0e
&uword r8 = $1b10
&uword r9 = $1b12
&uword r10 = $1b14
&uword r11 = $1b16
&uword r12 = $1b18
&uword r13 = $1b1a
&uword r14 = $1b1c
&uword r15 = $1b1e
&word r0s = $1b00
&word r1s = $1b02
&word r2s = $1b04
&word r3s = $1b06
&word r4s = $1b08
&word r5s = $1b0a
&word r6s = $1b0c
&word r7s = $1b0e
&word r8s = $1b10
&word r9s = $1b12
&word r10s = $1b14
&word r11s = $1b16
&word r12s = $1b18
&word r13s = $1b1a
&word r14s = $1b1c
&word r15s = $1b1e
&ubyte r0L = $1b00
&ubyte r1L = $1b02
&ubyte r2L = $1b04
&ubyte r3L = $1b06
&ubyte r4L = $1b08
&ubyte r5L = $1b0a
&ubyte r6L = $1b0c
&ubyte r7L = $1b0e
&ubyte r8L = $1b10
&ubyte r9L = $1b12
&ubyte r10L = $1b14
&ubyte r11L = $1b16
&ubyte r12L = $1b18
&ubyte r13L = $1b1a
&ubyte r14L = $1b1c
&ubyte r15L = $1b1e
&ubyte r0H = $1b01
&ubyte r1H = $1b03
&ubyte r2H = $1b05
&ubyte r3H = $1b07
&ubyte r4H = $1b09
&ubyte r5H = $1b0b
&ubyte r6H = $1b0d
&ubyte r7H = $1b0f
&ubyte r8H = $1b11
&ubyte r9H = $1b13
&ubyte r10H = $1b15
&ubyte r11H = $1b17
&ubyte r12H = $1b19
&ubyte r13H = $1b1b
&ubyte r14H = $1b1d
&ubyte r15H = $1b1f
&byte r0sL = $1b00
&byte r1sL = $1b02
&byte r2sL = $1b04
&byte r3sL = $1b06
&byte r4sL = $1b08
&byte r5sL = $1b0a
&byte r6sL = $1b0c
&byte r7sL = $1b0e
&byte r8sL = $1b10
&byte r9sL = $1b12
&byte r10sL = $1b14
&byte r11sL = $1b16
&byte r12sL = $1b18
&byte r13sL = $1b1a
&byte r14sL = $1b1c
&byte r15sL = $1b1e
&byte r0sH = $1b01
&byte r1sH = $1b03
&byte r2sH = $1b05
&byte r3sH = $1b07
&byte r4sH = $1b09
&byte r5sH = $1b0b
&byte r6sH = $1b0d
&byte r7sH = $1b0f
&byte r8sH = $1b11
&byte r9sH = $1b13
&byte r10sH = $1b15
&byte r11sH = $1b17
&byte r12sH = $1b19
&byte r13sH = $1b1b
&byte r14sH = $1b1d
&byte r15sH = $1b1f
}
+619
View File
@@ -0,0 +1,619 @@
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-64
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
%import syslib
%import conv
txt {
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
sub clear_screen() {
txt.chrout(147)
}
sub home() {
txt.chrout(19)
}
sub nl() {
txt.chrout('\n')
}
sub spc() {
txt.chrout(' ')
}
asmsub column(ubyte col @A) clobbers(A, X, Y) {
; ---- set the cursor on the given column (starting with 0) on the current line
%asm {{
sec
jsr c64.PLOT
tay
clc
jmp c64.PLOT
}}
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
; (assumes screen and color matrix are at their default addresses)
%asm {{
pha
tya
jsr clear_screencolors
pla
jsr clear_screenchars
rts
}}
}
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
; ---- clear the character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address)
%asm {{
ldy #250
- sta c64.Screen+250*0-1,y
sta c64.Screen+250*1-1,y
sta c64.Screen+250*2-1,y
sta c64.Screen+250*3-1,y
dey
bne -
rts
}}
}
asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
; ---- clear the character screen colors with the given color (leaves characters).
; (assumes color matrix is at the default address)
%asm {{
ldy #250
- sta c64.Colors+250*0-1,y
sta c64.Colors+250*1-1,y
sta c64.Colors+250*2-1,y
sta c64.Colors+250*3-1,y
dey
bne -
rts
}}
}
sub color (ubyte txtcol) {
c64.COLOR = txtcol
}
sub lowercase() {
c64.VMCSB |= 2
c128.VM1 |= 2
}
sub uppercase() {
c64.VMCSB &= ~2
c128.VM1 &= ~2
}
asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too
%asm {{
stx P8ZP_SCRATCH_REG
bcc _scroll_screen
+ ; scroll the screen and the color memory
ldx #0
ldy #38
-
.for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 1,x
sta c64.Screen + 40*row + 0,x
lda c64.Colors + 40*row + 1,x
sta c64.Colors + 40*row + 0,x
.next
inx
dey
bpl -
rts
_scroll_screen ; scroll only the screen memory
ldx #0
ldy #38
-
.for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 1,x
sta c64.Screen + 40*row + 0,x
.next
inx
dey
bpl -
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub scroll_right (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too
%asm {{
stx P8ZP_SCRATCH_REG
bcc _scroll_screen
+ ; scroll the screen and the color memory
ldx #38
-
.for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 0,x
sta c64.Screen + 40*row + 1,x
lda c64.Colors + 40*row + 0,x
sta c64.Colors + 40*row + 1,x
.next
dex
bpl -
rts
_scroll_screen ; scroll only the screen memory
ldx #38
-
.for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 0,x
sta c64.Screen + 40*row + 1,x
.next
dex
bpl -
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub scroll_up (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too
%asm {{
stx P8ZP_SCRATCH_REG
bcc _scroll_screen
+ ; scroll the screen and the color memory
ldx #39
-
.for row=1, row<=24, row+=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row-1),x
lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row-1),x
.next
dex
bpl -
rts
_scroll_screen ; scroll only the screen memory
ldx #39
-
.for row=1, row<=24, row+=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row-1),x
.next
dex
bpl -
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub scroll_down (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too
%asm {{
stx P8ZP_SCRATCH_REG
bcc _scroll_screen
+ ; scroll the screen and the color memory
ldx #39
-
.for row=23, row>=0, row-=1
lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row+1),x
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row+1),x
.next
dex
bpl -
rts
_scroll_screen ; scroll only the screen memory
ldx #39
-
.for row=23, row>=0, row-=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row+1),x
.next
dex
bpl -
ldx P8ZP_SCRATCH_REG
rts
}}
}
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use c64.CHROUT directly ofcourse.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to c64.CHROUT of that single char.
%asm {{
sta P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
ldy #0
- lda (P8ZP_SCRATCH_B1),y
beq +
jsr c64.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
stx P8ZP_SCRATCH_REG
jsr conv.ubyte2decimal
pha
tya
jsr c64.CHROUT
pla
jsr c64.CHROUT
txa
jsr c64.CHROUT
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
stx P8ZP_SCRATCH_REG
jsr conv.ubyte2decimal
_print_byte_digits
pha
cpy #'0'
beq +
tya
jsr c64.CHROUT
pla
jsr c64.CHROUT
jmp _ones
+ pla
cmp #'0'
beq _ones
jsr c64.CHROUT
_ones txa
jsr c64.CHROUT
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_b (byte value @ A) clobbers(A,Y) {
; ---- print the byte in A in decimal form, without left padding 0s
%asm {{
stx P8ZP_SCRATCH_REG
pha
cmp #0
bpl +
lda #'-'
jsr c64.CHROUT
+ pla
jsr conv.byte2decimal
jmp print_ub._print_byte_digits
}}
}
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
stx P8ZP_SCRATCH_REG
bcc +
pha
lda #'$'
jsr c64.CHROUT
pla
+ jsr conv.ubyte2hex
jsr c64.CHROUT
tya
jsr c64.CHROUT
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_B1
bcc +
lda #'%'
jsr c64.CHROUT
+ ldy #8
- lda #'0'
asl P8ZP_SCRATCH_B1
bcc +
lda #'1'
+ jsr c64.CHROUT
dey
bne -
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
pha
tya
jsr print_ubbin
pla
clc
jmp print_ubbin
}}
}
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
pha
tya
jsr print_ubhex
pla
clc
jmp print_ubhex
}}
}
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
stx P8ZP_SCRATCH_REG
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq +
jsr c64.CHROUT
iny
bne -
+ ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
stx P8ZP_SCRATCH_REG
jsr conv.uword2decimal
ldx P8ZP_SCRATCH_REG
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit
jsr c64.CHROUT
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero
lda #'0'
jmp c64.CHROUT
}}
}
asmsub print_w (word value @ AY) clobbers(A,Y) {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{
cpy #0
bpl +
pha
lda #'-'
jsr c64.CHROUT
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
+ jmp print_uw
}}
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0 ; char counter = 0
- jsr c64.CHRIN
cmp #$0d ; return (ascii 13) pressed?
beq + ; yes, end.
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
iny
bne -
+ lda #0
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
rts
}}
}
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y) {
; ---- sets the character in the screen matrix at the given position
%asm {{
pha
tya
asl a
tay
lda _screenrows+1,y
sta _mod+2
txa
clc
adc _screenrows,y
sta _mod+1
bcc +
inc _mod+2
+ pla
_mod sta $ffff ; modified
rts
_screenrows .word $0400 + range(0, 1000, 40)
}}
}
asmsub getchr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
; ---- get the character in the screen matrix at the given location
%asm {{
pha
tya
asl a
tay
lda setchr._screenrows+1,y
sta _mod+2
pla
clc
adc setchr._screenrows,y
sta _mod+1
bcc _mod
inc _mod+2
_mod lda $ffff ; modified
rts
}}
}
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A, Y) {
; ---- set the color in A on the screen matrix at the given position
%asm {{
pha
tya
asl a
tay
lda _colorrows+1,y
sta _mod+2
txa
clc
adc _colorrows,y
sta _mod+1
bcc +
inc _mod+2
+ pla
_mod sta $ffff ; modified
rts
_colorrows .word $d800 + range(0, 1000, 40)
}}
}
asmsub getclr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
; ---- get the color in the screen color matrix at the given location
%asm {{
pha
tya
asl a
tay
lda setclr._colorrows+1,y
sta _mod+2
pla
clc
adc setclr._colorrows,y
sta _mod+1
bcc _mod
inc _mod+2
_mod lda $ffff ; modified
rts
}}
}
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
; ---- set char+color at the given position on the screen
%asm {{
lda row
asl a
tay
lda setchr._screenrows+1,y
sta _charmod+2
adc #$d4
sta _colormod+2
lda setchr._screenrows,y
clc
adc column
sta _charmod+1
sta _colormod+1
bcc +
inc _charmod+2
inc _colormod+2
+ lda char
_charmod sta $ffff ; modified
lda charcolor
_colormod sta $ffff ; modified
rts
}}
}
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- safe wrapper around PLOT kernal routine, to save the X register.
%asm {{
stx P8ZP_SCRATCH_REG
tax
clc
jsr c64.PLOT
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub width() clobbers(X,Y) -> ubyte @A {
; -- returns the text screen width (number of columns)
%asm {{
jsr c64.SCREEN
txa
rts
}}
}
asmsub height() clobbers(X, Y) -> ubyte @A {
; -- returns the text screen height (number of rows)
%asm {{
jsr c64.SCREEN
tya
rts
}}
}
}
+1 -1
View File
@@ -474,7 +474,7 @@ _setup_raster_irq
sys {
; ------- lowlevel system routines --------
const ubyte target = 64 ; compilation target specifier. 64 = C64, 16 = CommanderX16.
const ubyte target = 64 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
asmsub reset_system() {
+12 -13
View File
@@ -7,32 +7,31 @@
cx16diskio {
; Use kernal LOAD routine to load the given program file in memory.
; This mimimics Basic's LOAD "filename",drive / LOAD "filename",drive,1
; This is similar to Basic's LOAD "filename",drive / LOAD "filename",drive,1
; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
; If you specify a custom address_override, the first 2 bytes in the file are ignored
; and the rest is loaded at the given location in memory.
; Returns the number of bytes loaded (truncated to 16 bits, if the file is larger than 64 Kb,
; you'll have to compensate yourself by checking the ram banks).
; Returns the end load address+1 if successful or 0 if a load error occurred.
; You can use the load_size() function to calcuate the size of the file that was loaded.
sub load(ubyte drivenumber, uword filenameptr, ubyte bank, uword address_override) -> uword {
cx16.rambank(bank)
uword size = diskio.load(drivenumber, filenameptr, address_override)
if size
return size + $2000 * (cx16.getrambank() - bank)
return 0
return diskio.load(drivenumber, filenameptr, address_override)
}
; Use kernal LOAD routine to load the given file in memory.
; INCLUDING the first 2 bytes in the file: no program header is assumed in the file.
; This is different from Basic's LOAD instruction which always skips the first two bytes.
; The load address is mandatory. Returns the number of bytes loaded.
; Returns the number of bytes loaded (truncated to 16 bits, if the file is larger than 64 Kb,
; you'll have to compensate yourself by checking the ram banks).
; Returns the end load address+1 if successful or 0 if a load error occurred.
; You can use the load_size() function to calcuate the size of the file that was loaded.
sub load_raw(ubyte drivenumber, uword filenameptr, ubyte bank, uword address) -> uword {
cx16.rambank(bank)
uword size = diskio.load_raw(drivenumber, filenameptr, address)
if size
return size + $2000 * (cx16.getrambank() - bank)
return 0
return diskio.load_raw(drivenumber, filenameptr, address)
}
; For use directly after a load or load_raw call (don't mess with the ram bank yet):
; Calculates the number of bytes loaded (files > 64Kb ar truncated to 16 bits)
sub load_size(ubyte startbank, uword startaddress, uword endaddress) -> uword {
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
}
}
+2 -2
View File
@@ -11,7 +11,7 @@ c64 {
; STROUT --> use txt.print
; CLEARSCR -> use txt.clear_screen
; HOMECRSR -> use txt.plot
; HOMECRSR -> use txt.home or txt.plot
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
@@ -811,7 +811,7 @@ asmsub set_rasterline(uword line @AY) {
sys {
; ------- lowlevel system routines --------
const ubyte target = 16 ; compilation target specifier. 64 = C64, 16 = CommanderX16.
const ubyte target = 16 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
asmsub reset_system() {
+6 -6
View File
@@ -20,12 +20,12 @@ cx16logo {
}
str[] logo_lines = [
"\uf10d\uf11a\uf139\uf11b \uf11a\uf13a\uf11b\n",
"\uf10b\uf11a▎\uf139\uf11b \uf11a\uf13a\uf130\uf11b\n",
"\uf10f\uf11a▌ \uf139\uf11b \uf11a\uf13a \uf11b▌\n",
"\uf102 \uf132\uf11a▖\uf11b \uf11a▗\uf11b\uf132\n",
"\uf10e ▂\uf11a▘\uf11b \uf11a▝\uf11b▂\n",
"\uf104 \uf11a \uf11b\uf13a\uf11b \uf139\uf11a \uf11b\n",
"\uf10d\uf11a\uf139\uf11b \uf11a\uf13a\uf11b",
"\uf10b\uf11a▎\uf139\uf11b \uf11a\uf13a\uf130\uf11b",
"\uf10f\uf11a▌ \uf139\uf11b \uf11a\uf13a \uf11b▌",
"\uf102 \uf132\uf11a▖\uf11b \uf11a▗\uf11b\uf132",
"\uf10e ▂\uf11a▘\uf11b \uf11a▝\uf11b▂",
"\uf104 \uf11a \uf11b\uf13a\uf11b \uf139\uf11a \uf11b",
"\uf101\uf130\uf13a \uf139▎\uf100"
]
}
+11 -17
View File
@@ -434,11 +434,11 @@ io_error:
}
; Use kernal LOAD routine to load the given program file in memory.
; This mimimics Basic's LOAD "filename",drive / LOAD "filename",drive,1
; This is similar to Basic's LOAD "filename",drive / LOAD "filename",drive,1
; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
; If you specify a custom address_override, the first 2 bytes in the file are ignored
; and the rest is loaded at the given location in memory.
; Returns the number of bytes loaded.
; Returns the end load address+1 if successful or 0 if a load error occurred.
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
; (which is possible on the Commander X16), the returned size is not correct,
; because it doesn't take the number of ram banks into account.
@@ -446,7 +446,7 @@ io_error:
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
c64.SETNAM(string.length(filenameptr), filenameptr)
ubyte secondary = 1
uword end_of_load = 0
cx16.r1 = 0
if address_override
secondary = 0
c64.SETLFS(1, drivenumber, secondary)
@@ -457,24 +457,21 @@ io_error:
ldy address_override+1
jsr c64.LOAD
bcs +
stx end_of_load
sty end_of_load+1
stx cx16.r1
sty cx16.r1+1
+ ldx P8ZP_SCRATCH_REG
}}
c64.CLRCHN()
c64.CLOSE(1)
if end_of_load
return end_of_load - address_override ; not correct when the file spans multiple RAM banks
return 0
return cx16.r1
}
; Use kernal LOAD routine to load the given file in memory.
; INCLUDING the first 2 bytes in the file: no program header is assumed in the file.
; This is different from Basic's LOAD instruction which always skips the first two bytes.
; The load address is mandatory. Returns the number of bytes loaded.
; The load address is mandatory.
; Returns the end load address+1 if successful or 0 if a load error occurred.
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
; (which is possible on the Commander X16), the returned size is not correct,
; because it doesn't take the number of ram banks into account.
@@ -482,15 +479,12 @@ io_error:
sub load_raw(ubyte drivenumber, uword filenameptr, uword address) -> uword {
if not f_open(drivenumber, filenameptr)
return 0
uword size = f_read(address, 2)
cx16.r1 = f_read(address, 2)
f_close()
if size!=2
if cx16.r1!=2
return 0
address += 2
size = load(drivenumber, filenameptr, address)
if size
return size+2
return 0
return load(drivenumber, filenameptr, address)
}
sub delete(ubyte drivenumber, uword filenameptr) {
+42
View File
@@ -1083,3 +1083,45 @@ strlen .proc
bne -
+ rts
.pend
containment_bytearray .proc
; -- check if a value exists in a byte array.
; parameters: P8ZP_SCRATCH_W1: address of the byte array, A = byte to check, Y = length of array (>=1).
; returns boolean 0/1 in A.
dey
- cmp (P8ZP_SCRATCH_W1),y
beq +
dey
cpy #255
bne -
lda #0
rts
+ lda #1
rts
.pend
containment_wordarray .proc
; -- check if a value exists in a word array.
; parameters: P8ZP_SCRATCH_W1: value to check, P8ZP_SCRATCH_W2: address of the word array, Y = length of array (>=1).
; returns boolean 0/1 in A.
dey
tya
asl a
tay
- lda P8ZP_SCRATCH_W1
cmp (P8ZP_SCRATCH_W2),y
bne +
lda P8ZP_SCRATCH_W1+1
iny
cmp (P8ZP_SCRATCH_W2),y
beq _found
+ dey
dey
cpy #254
bne -
lda #0
rts
_found lda #1
rts
.pend
+1 -1
View File
@@ -1 +1 @@
7.5
7.6
+11 -4
View File
@@ -2,11 +2,12 @@ package prog8
import kotlinx.cli.*
import prog8.ast.base.AstException
import prog8.codegen.target.C128Target
import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.codegen.target.C64Target
import prog8.codegen.target.Cx16Target
import java.io.File
import java.nio.file.FileSystems
import java.nio.file.Path
@@ -36,11 +37,13 @@ private fun compileMain(args: Array<String>): Boolean {
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
val dontReinitGlobals by cli.option(ArgType.Boolean, fullName = "noreinit", description = "don't create code to reinitialize globals on multiple runs of the program (experimental!)")
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
val asmListfile by cli.option(ArgType.Boolean, fullName = "asmlist", description = "make the assembler produce a listing file as well")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.name}', '${C128Target.name}', '${Cx16Target.name}')").default(C64Target.name)
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
@@ -67,7 +70,7 @@ private fun compileMain(args: Array<String>): Boolean {
if(srcdirs.firstOrNull()!=".")
srcdirs.add(0, ".")
if (compilationTarget != C64Target.name && compilationTarget != Cx16Target.name) {
if (compilationTarget !in setOf(C64Target.name, C128Target.name, Cx16Target.name)) {
System.err.println("Invalid compilation target: $compilationTarget")
return false
}
@@ -85,9 +88,11 @@ private fun compileMain(args: Array<String>): Boolean {
filepath,
dontOptimize != true,
optimizeFloatExpressions == true,
dontReinitGlobals == true,
dontWriteAssembly != true,
slowCodegenWarnings == true,
quietAssembler == true,
asmListfile == true,
compilationTarget,
srcdirs,
outputPath
@@ -133,9 +138,11 @@ private fun compileMain(args: Array<String>): Boolean {
filepath,
dontOptimize != true,
optimizeFloatExpressions == true,
dontReinitGlobals == true,
dontWriteAssembly != true,
slowCodegenWarnings == true,
quietAssembler == true,
asmListfile == true,
compilationTarget,
srcdirs,
outputPath
@@ -11,7 +11,7 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compiler.astprocessing.isSubroutineParameter
import prog8.compiler.target.AssemblyError
import prog8.codegen.target.AssemblyError
import prog8.compilerinterface.*
import prog8.optimizer.getTempVarName
@@ -31,6 +31,18 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
varsList.add(decl.name to decl)
}
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
throw FatalAstException("break should have been replaced by goto $breakStmt")
}
override fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
throw FatalAstException("while should have been converted to jumps")
}
override fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
throw FatalAstException("do..until should have been converted to jumps")
}
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
// move all subroutines to the bottom of the block
val subs = block.statements.filterIsInstance<Subroutine>()
@@ -55,15 +67,15 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
&& !assignment.target.isIOAddress(options.compTarget.machine)) {
val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null && binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
if(binExpr!=null && binExpr.inferType(program) istype DataType.FLOAT && !options.optimizeFloatExpressions)
return noModifications
if (binExpr != null && binExpr.operator !in comparisonOperators) {
if (binExpr != null && binExpr.operator !in ComparisonOperators) {
if (binExpr.left !is BinaryExpression) {
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) {
if(binExpr.operator in AssociativeOperators) {
// A = <something-without-A> <associativeoperator> <otherthing-with-A>
// use the other part of the expression to split.
val sourceDt = binExpr.right.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
@@ -127,7 +139,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
if (subroutineStmtIdx > 0) {
val prevStmt = outerStatements[subroutineStmtIdx-1]
if(outerScope !is Block
&& (prevStmt !is Jump || prevStmt.isGosub)
&& (prevStmt !is Jump)
&& prevStmt !is Subroutine
&& prevStmt !is Return) {
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
@@ -181,20 +193,19 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
return noModifications
}
@Suppress("DuplicatedCode")
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
val prefixExpr = ifStatement.condition as? PrefixExpression
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
val prefixExpr = ifElse.condition as? PrefixExpression
if(prefixExpr!=null && prefixExpr.operator=="not") {
// if not x -> if x==0
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, ifElse.condition.position), ifElse.condition.position)
return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse))
}
val binExpr = ifStatement.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
val binExpr = ifElse.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in ComparisonOperators) {
// if x -> if x!=0, if x+5 -> if x+5 != 0
val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
val booleanExpr = BinaryExpression(ifElse.condition, "!=", NumericLiteralValue.optimalInteger(0, ifElse.condition.position), ifElse.condition.position)
return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse))
}
if((binExpr.left as? NumericLiteralValue)?.number==0.0 &&
@@ -208,11 +219,11 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
val modifications = mutableListOf<IAstModification>()
if(simplify.rightVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
modifications += IAstModification.InsertBefore(ifStatement, simplify.rightVarAssignment, parent as IStatementContainer)
modifications += IAstModification.InsertBefore(ifElse, simplify.rightVarAssignment, parent as IStatementContainer)
}
if(simplify.leftVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
modifications += IAstModification.InsertBefore(ifStatement, simplify.leftVarAssignment, parent as IStatementContainer)
modifications += IAstModification.InsertBefore(ifElse, simplify.leftVarAssignment, parent as IStatementContainer)
}
return modifications
@@ -238,11 +249,18 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
var rightAssignment: Assignment? = null
var rightOperandReplacement: Expression? = null
val separateLeftExpr = !expr.left.isSimple && expr.left !is IFunctionCall
val separateRightExpr = !expr.right.isSimple && expr.right !is IFunctionCall
val separateLeftExpr = !expr.left.isSimple && expr.left !is IFunctionCall && expr.left !is ContainmentCheck
val separateRightExpr = !expr.right.isSimple && expr.right !is IFunctionCall && expr.right !is ContainmentCheck
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
if(!leftDt.isInteger || !rightDt.isInteger) {
// we can't reasonably simplify non-integer expressions
return CondExprSimplificationResult(null, null, null, null)
}
if(separateLeftExpr) {
val name = getTempVarName(expr.left.inferType(program))
val name = getTempVarName(leftDt)
leftOperandReplacement = IdentifierReference(name, expr.position)
leftAssignment = Assignment(
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
@@ -251,12 +269,11 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
)
}
if(separateRightExpr) {
val dt = expr.right.inferType(program)
val name = when {
dt.istype(DataType.UBYTE) -> listOf("prog8_lib","retval_interm_ub")
dt.istype(DataType.UWORD) -> listOf("prog8_lib","retval_interm_uw")
dt.istype(DataType.BYTE) -> listOf("prog8_lib","retval_interm_b2")
dt.istype(DataType.WORD) -> listOf("prog8_lib","retval_interm_w2")
rightDt istype DataType.UBYTE -> listOf("prog8_lib","retval_interm_ub")
rightDt istype DataType.UWORD -> listOf("prog8_lib","retval_interm_uw")
rightDt istype DataType.BYTE -> listOf("prog8_lib","retval_interm_b2")
rightDt istype DataType.WORD -> listOf("prog8_lib","retval_interm_w2")
else -> throw AssemblyError("invalid dt")
}
rightOperandReplacement = IdentifierReference(name, expr.position)
@@ -272,74 +289,6 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
)
}
@Suppress("DuplicatedCode")
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
val prefixExpr = untilLoop.condition as? PrefixExpression
if(prefixExpr!=null && prefixExpr.operator=="not") {
// until not x -> until x==0
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
}
val binExpr = untilLoop.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
// until x -> until x!=0, until x+5 -> until x+5 != 0
val booleanExpr = BinaryExpression(untilLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
}
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.
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
val simplify = simplifyConditionalExpression(binExpr)
val modifications = mutableListOf<IAstModification>()
if(simplify.rightVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
modifications += IAstModification.InsertLast(simplify.rightVarAssignment, untilLoop.body)
}
if(simplify.leftVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
modifications += IAstModification.InsertLast(simplify.leftVarAssignment, untilLoop.body)
}
return modifications
}
@Suppress("DuplicatedCode")
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
val prefixExpr = whileLoop.condition as? PrefixExpression
if(prefixExpr!=null && prefixExpr.operator=="not") {
// while not x -> while x==0
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
}
val binExpr = whileLoop.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
// while x -> while x!=0, while x+5 -> while x+5 != 0
val booleanExpr = BinaryExpression(whileLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
}
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.
// make sure to evaluate it only once, but also right at entry of the while loop
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
// NOTE: this is nasty for a while-statement as the condition occurs at the top of the loop
// so the expression needs to be evaluated also before the loop is entered...
// but I don't want to duplicate the expression.
// val simplify = simplifyConditionalExpression(binExpr)
return noModifications
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource==listOf("cmp")) {
// if the datatype of the arguments of cmp() are different, cast the byte one to word.
@@ -392,12 +341,12 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
complexArrayIndexedExpressions.add(arrayIndexedExpression)
}
override fun visit(branchStatement: BranchStatement) {}
override fun visit(branch: Branch) {}
override fun visit(forLoop: ForLoop) {}
override fun visit(ifStatement: IfStatement) {
ifStatement.condition.accept(this)
override fun visit(ifElse: IfElse) {
ifElse.condition.accept(this)
}
override fun visit(untilLoop: UntilLoop) {
+28 -16
View File
@@ -9,10 +9,11 @@ import prog8.ast.base.Position
import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.Directive
import prog8.codegen.target.C128Target
import prog8.compiler.astprocessing.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.codegen.target.C64Target
import prog8.codegen.target.Cx16Target
import prog8.codegen.target.cpu6502.codegen.AsmGen
import prog8.compilerinterface.*
import prog8.optimizer.*
import prog8.parser.ParseError
@@ -31,9 +32,11 @@ class CompilationResult(val success: Boolean,
class CompilerArguments(val filepath: Path,
val optimize: Boolean,
val optimizeFloatExpressions: Boolean,
val dontReinitGlobals: Boolean,
val writeAssembly: Boolean,
val slowCodegenWarnings: Boolean,
val quietAssembler: Boolean,
val asmListfile: Boolean,
val compilationTarget: String,
val sourceDirs: List<String> = emptyList(),
val outputDir: Path = Path(""),
@@ -50,6 +53,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
val compTarget =
when(args.compilationTarget) {
C64Target.name -> C64Target
C128Target.name -> C128Target
Cx16Target.name -> Cx16Target
else -> throw IllegalArgumentException("invalid compilation target")
}
@@ -59,14 +63,20 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
// import main module and everything it needs
val (programresult, compilationOptions, imported) = parseImports(args.filepath, args.errors, compTarget, args.sourceDirs)
with(compilationOptions) {
this.slowCodegenWarnings = args.slowCodegenWarnings
this.optimize = args.optimize
this.optimizeFloatExpressions = optimizeFloatExpr
slowCodegenWarnings = args.slowCodegenWarnings
optimize = args.optimize
optimizeFloatExpressions = optimizeFloatExpr
dontReinitGlobals = args.dontReinitGlobals
asmQuiet = args.quietAssembler
asmListfile = args.asmListfile
}
program = programresult
importedFiles = imported
processAst(program, args.errors, compilationOptions)
if (compilationOptions.optimize)
if (compilationOptions.optimize) {
// println("*********** AST RIGHT BEFORE OPTIMIZING *************")
// printProgram(program)
optimizeAst(
program,
compilationOptions,
@@ -74,13 +84,14 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
BuiltinFunctionsFacade(BuiltinFunctions),
compTarget
)
}
postprocessAst(program, args.errors, compilationOptions)
// println("*********** AST BEFORE ASSEMBLYGEN *************")
// printProgram(program)
if (args.writeAssembly) {
when (val result = writeAssembly(program, args.errors, args.outputDir, args.quietAssembler, compilationOptions)) {
when (val result = writeAssembly(program, args.errors, args.outputDir, compilationOptions)) {
is WriteAssemblyResult.Ok -> programName = result.filename
is WriteAssemblyResult.Fail -> {
System.err.println(result.error)
@@ -255,7 +266,7 @@ 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)
program.preprocessAst(program, errors)
program.checkIdentifiers(errors, program, 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
@@ -265,9 +276,11 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
program.charLiteralsToUByteLiterals(compilerOptions.compTarget)
program.constantFold(errors, compilerOptions.compTarget)
errors.report()
program.desugaring(errors)
errors.report()
program.reorderStatements(errors, compilerOptions)
errors.report()
program.addTypecasts(errors)
program.addTypecasts(errors, compilerOptions)
errors.report()
program.variousCleanups(program, errors)
errors.report()
@@ -295,21 +308,21 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
if (optsDone1 + optsDone2 + optsDone3 == 0)
break
}
errors.report()
}
private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
program.addTypecasts(errors)
program.desugaring(errors)
program.addTypecasts(errors, compilerOptions)
errors.report()
program.variousCleanups(program, errors)
program.checkValid(errors, compilerOptions) // check if final tree is still valid
errors.report()
val callGraph = CallGraph(program)
callGraph.checkRecursiveCalls(errors)
errors.report()
program.verifyFunctionArgTypes()
program.moveMainAndStartToFirst()
program.checkValid(errors, compilerOptions) // check if final tree is still valid
errors.report()
}
private sealed class WriteAssemblyResult {
@@ -320,7 +333,6 @@ private sealed class WriteAssemblyResult {
private fun writeAssembly(program: Program,
errors: IErrorReporter,
outputDir: Path,
quietAssembler: Boolean,
compilerOptions: CompilationOptions
): WriteAssemblyResult {
// asm generation directly from the Ast
@@ -340,7 +352,7 @@ private fun writeAssembly(program: Program,
errors.report()
return if(assembly.valid && errors.noErrors()) {
val assemblerReturnStatus = assembly.assemble(quietAssembler, compilerOptions)
val assemblerReturnStatus = assembly.assemble(compilerOptions)
if(assemblerReturnStatus!=0)
WriteAssemblyResult.Fail("assembler step failed with return code $assemblerReturnStatus")
else {
@@ -80,10 +80,10 @@ internal class AstChecker(private val program: Program,
super.visit(returnStmt)
}
override fun visit(ifStatement: IfStatement) {
if(!ifStatement.condition.inferType(program).isInteger)
errors.err("condition value should be an integer type", ifStatement.condition.position)
super.visit(ifStatement)
override fun visit(ifElse: IfElse) {
if(!ifElse.condition.inferType(program).isInteger)
errors.err("condition value should be an integer type", ifElse.condition.position)
super.visit(ifElse)
}
override fun visit(forLoop: ForLoop) {
@@ -165,10 +165,10 @@ internal class AstChecker(private val program: Program,
if(ident!=null) {
val targetStatement = checkFunctionOrLabelExists(ident, jump)
if(targetStatement!=null) {
if(targetStatement is BuiltinFunctionStatementPlaceholder)
if(targetStatement is BuiltinFunctionPlaceholder)
errors.err("can't jump to a builtin function", jump.position)
}
if(!jump.isGosub && targetStatement is Subroutine && targetStatement.parameters.any()) {
if(targetStatement is Subroutine && targetStatement.parameters.any()) {
errors.err("can't jump to a subroutine that takes parameters", jump.position)
}
}
@@ -179,6 +179,22 @@ internal class AstChecker(private val program: Program,
super.visit(jump)
}
override fun visit(gosub: GoSub) {
val ident = gosub.identifier
if(ident!=null) {
val targetStatement = checkFunctionOrLabelExists(ident, gosub)
if(targetStatement!=null) {
if(targetStatement is BuiltinFunctionPlaceholder)
errors.err("can't gosub to a builtin function", gosub.position)
}
}
val addr = gosub.address
if(addr!=null && addr > 65535u)
errors.err("gosub address must be valid integer 0..\$ffff", gosub.position)
super.visit(gosub)
}
override fun visit(block: Block) {
val addr = block.address
if(addr!=null && addr>65535u) {
@@ -193,7 +209,7 @@ internal class AstChecker(private val program: Program,
is VarDecl,
is InlineAssembly,
is IStatementContainer,
is NopStatement -> true
is Nop -> true
is Assignment -> {
val target = statement.target.identifier!!.targetStatement(program)
target === statement.previousSibling() // an initializer assignment is okay
@@ -226,8 +242,7 @@ internal class AstChecker(private val program: Program,
count++
}
override fun visit(jump: Jump) {
if(!jump.isGosub)
count++
count++
}
override fun visit(inlineAssembly: InlineAssembly) {
@@ -488,7 +503,7 @@ internal class AstChecker(private val program: Program,
} else {
val sourceDatatype = assignment.value.inferType(program)
if (sourceDatatype.isUnknown) {
if (assignment.value !is FunctionCall)
if (assignment.value !is FunctionCallExpr)
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
} else {
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE),
@@ -519,13 +534,18 @@ internal class AstChecker(private val program: Program,
err("const modifier can only be used on numeric types (byte, word, float)")
}
// @zp can only occur on integers
if(decl.datatype !in IntegerDatatypes) {
if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)
errors.warn("this datatype can't be placed in zeropage", decl.position)
if(decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
errors.err("this datatype can't be placed in zeropage", decl.position)
}
// FLOATS enabled?
if(!compilerOptions.floats && decl.datatype.oneOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY)
err("floating point used, but that is not enabled via options")
if(decl.datatype == DataType.FLOAT && (decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE))
errors.warn("floating point values won't be placed in Zeropage due to size constraints", decl.position)
// ARRAY without size specifier MUST have an iterable initializer value
if(decl.isArray && decl.arraysize==null) {
if(decl.type== VarDeclType.MEMORY)
@@ -854,6 +874,7 @@ internal class AstChecker(private val program: Program,
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
errors.err("bitwise operator can only be used on integer operands", expr.right.position)
}
"in" -> throw FatalAstException("in expression should have been replaced by containmentcheck")
}
if(leftDt !in NumericDatatypes && leftDt != DataType.STR)
@@ -862,6 +883,14 @@ internal class AstChecker(private val program: Program,
errors.err("right operand is not numeric or str", expr.right.position)
if(leftDt!=rightDt)
errors.err("left and right operands aren't the same type", expr.left.position)
if(expr.operator !in ComparisonOperators) {
if (leftDt == DataType.STR && rightDt == DataType.STR || leftDt in ArrayDatatypes && rightDt in ArrayDatatypes) {
// str+str and str*number have already been const evaluated before we get here.
errors.err("no computational expressions with strings or arrays are possible", expr.position)
}
}
}
override fun visit(typecast: TypecastExpression) {
@@ -907,28 +936,28 @@ internal class AstChecker(private val program: Program,
}
}
override fun visit(functionCall: FunctionCall) {
override fun visit(functionCallExpr: FunctionCallExpr) {
// this function call is (part of) an expression, which should be in a statement somewhere.
val stmtOfExpression = findParentNode<Statement>(functionCall)
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCall.position}")
val stmtOfExpression = findParentNode<Statement>(functionCallExpr)
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}")
val targetStatement = checkFunctionOrLabelExists(functionCall.target, stmtOfExpression)
val targetStatement = checkFunctionOrLabelExists(functionCallExpr.target, stmtOfExpression)
if(targetStatement!=null)
checkFunctionCall(targetStatement, functionCall.args, functionCall.position)
checkFunctionCall(targetStatement, functionCallExpr.args, functionCallExpr.position)
// warn about sgn(unsigned) this is likely a mistake
if(functionCall.target.nameInSource.last()=="sgn") {
val sgnArgType = functionCall.args.first().inferType(program)
if(functionCallExpr.target.nameInSource.last()=="sgn") {
val sgnArgType = functionCallExpr.args.first().inferType(program)
if(sgnArgType istype DataType.UBYTE || sgnArgType istype DataType.UWORD)
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position)
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCallExpr.args.first().position)
}
val error = VerifyFunctionArgTypes.checkTypes(functionCall, program)
val error = VerifyFunctionArgTypes.checkTypes(functionCallExpr, program)
if(error!=null)
errors.err(error, functionCall.position)
errors.err(error, functionCallExpr.position)
// check the functions that return multiple returnvalues.
val stmt = functionCall.target.targetStatement(program)
val stmt = functionCallExpr.target.targetStatement(program)
if (stmt is Subroutine) {
if (stmt.returntypes.size > 1) {
// Currently, it's only possible to handle ONE (or zero) return values from a subroutine.
@@ -939,12 +968,9 @@ internal class AstChecker(private val program: Program,
// if the asmsub returns multiple values and one of them is via a status register bit,
// it *is* possible to handle them by just actually assigning the register value and
// dealing with the status bit as just being that, the status bit after the call.
val (returnRegisters, returnStatusflags) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
if (returnRegisters.isEmpty() || returnRegisters.size == 1) {
if (returnStatusflags.any())
errors.warn("this asmsub also has one or more return 'values' in one of the status flags", functionCall.position)
} else {
errors.err("It's not possible to store the multiple result values of this asmsub call; you should use a small block of custom inline assembly for this.", functionCall.position)
val (returnRegisters, _) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
if (returnRegisters.size>1) {
errors.err("It's not possible to store the multiple result values of this asmsub call; you should use a small block of custom inline assembly for this.", functionCallExpr.position)
}
}
}
@@ -952,18 +978,18 @@ internal class AstChecker(private val program: Program,
// functions that don't return a value, can't be used in an expression or assignment
if(targetStatement is Subroutine) {
if(targetStatement.returntypes.isEmpty()) {
if(functionCall.parent is Expression || functionCall.parent is Assignment)
errors.err("subroutine doesn't return a value", functionCall.position)
if(functionCallExpr.parent is Expression || functionCallExpr.parent is Assignment)
errors.err("subroutine doesn't return a value", functionCallExpr.position)
}
}
else if(targetStatement is BuiltinFunctionStatementPlaceholder) {
if(builtinFunctionReturnType(targetStatement.name, functionCall.args, program).isUnknown) {
if(functionCall.parent is Expression || functionCall.parent is Assignment)
errors.err("function doesn't return a value", functionCall.position)
else if(targetStatement is BuiltinFunctionPlaceholder) {
if(builtinFunctionReturnType(targetStatement.name, functionCallExpr.args, program).isUnknown) {
if(functionCallExpr.parent is Expression || functionCallExpr.parent is Assignment)
errors.err("function doesn't return a value", functionCallExpr.position)
}
}
super.visit(functionCall)
super.visit(functionCallExpr)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
@@ -1019,7 +1045,7 @@ internal class AstChecker(private val program: Program,
if(target is Label && args.isNotEmpty())
errors.err("cannot use arguments when calling a label", position)
if(target is BuiltinFunctionStatementPlaceholder) {
if(target is BuiltinFunctionPlaceholder) {
if(target.name=="swap") {
// swap() is a bit weird because this one is translated into an operations directly, instead of being a function call
val dt1 = args[0].inferType(program)
@@ -1055,8 +1081,8 @@ internal class AstChecker(private val program: Program,
var ident: IdentifierReference? = null
if(arg.value is IdentifierReference)
ident = arg.value as IdentifierReference
else if(arg.value is FunctionCall) {
val fcall = arg.value as FunctionCall
else if(arg.value is FunctionCallExpr) {
val fcall = arg.value as FunctionCallExpr
if(fcall.target.nameInSource == listOf("lsb") || fcall.target.nameInSource == listOf("msb"))
ident = fcall.args[0] as? IdentifierReference
}
@@ -1139,11 +1165,11 @@ internal class AstChecker(private val program: Program,
super.visit(arrayIndexedExpression)
}
override fun visit(whenStatement: WhenStatement) {
if(!whenStatement.condition.inferType(program).isInteger)
errors.err("when condition must be an integer value", whenStatement.position)
override fun visit(whenStmt: When) {
if(!whenStmt.condition.inferType(program).isInteger)
errors.err("when condition must be an integer value", whenStmt.position)
val tally = mutableSetOf<Int>()
for((choices, choiceNode) in whenStatement.choiceValues(program)) {
for((choices, choiceNode) in whenStmt.choiceValues(program)) {
if(choices!=null) {
for (c in choices) {
if(c in tally)
@@ -1154,14 +1180,14 @@ internal class AstChecker(private val program: Program,
}
}
if(whenStatement.choices.isEmpty())
errors.err("empty when statement", whenStatement.position)
if(whenStmt.choices.isEmpty())
errors.err("empty when statement", whenStmt.position)
super.visit(whenStatement)
super.visit(whenStmt)
}
override fun visit(whenChoice: WhenChoice) {
val whenStmt = whenChoice.parent as WhenStatement
val whenStmt = whenChoice.parent as When
if(whenChoice.values!=null) {
val conditionType = whenStmt.condition.inferType(program)
if(!conditionType.isKnown)
@@ -1181,9 +1207,20 @@ internal class AstChecker(private val program: Program,
super.visit(whenChoice)
}
override fun visit(containment: ContainmentCheck) {
if(!containment.iterable.inferType(program).isIterable)
errors.err("value set for containment check must be an iterable type", containment.iterable.position)
if(containment.parent is BinaryExpression)
errors.err("containment check is currently not supported in complex expressions", containment.position)
// TODO check that iterable contains the same types as the element that is searched
super.visit(containment)
}
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
when (val targetStatement = target.targetStatement(program)) {
is Label, is Subroutine, is BuiltinFunctionStatementPlaceholder -> return targetStatement
is Label, is Subroutine, is BuiltinFunctionPlaceholder -> return targetStatement
null -> errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)
else -> errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", statement.position)
}
@@ -1407,7 +1444,7 @@ internal fun checkUnusedReturnValues(call: FunctionCallStatement, target: Statem
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) {
} else if (target is BuiltinFunctionPlaceholder) {
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)
@@ -16,6 +16,8 @@ import prog8.compilerinterface.IStringEncoding
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
val parentChecker = ParentNodeChecker()
parentChecker.visit(this)
val checker = AstChecker(this, errors, compilerOptions)
checker.visit(this)
}
@@ -53,19 +55,25 @@ internal fun Program.charLiteralsToUByteLiterals(enc: IStringEncoding) {
walker.applyModifications()
}
internal fun Program.addTypecasts(errors: IErrorReporter) {
val caster = TypecastsAdder(this, errors)
internal fun Program.addTypecasts(errors: IErrorReporter, options: CompilationOptions) {
val caster = TypecastsAdder(this, options, errors)
caster.visit(this)
caster.applyModifications()
}
fun Program.desugaring(errors: IErrorReporter): Int {
val desugar = CodeDesugarer(this, errors)
desugar.visit(this)
return desugar.applyModifications()
}
internal fun Program.verifyFunctionArgTypes() {
val fixer = VerifyFunctionArgTypes(this)
fixer.visit(this)
}
internal fun Program.preprocessAst(program: Program) {
val transforms = AstPreprocessor(program)
internal fun Program.preprocessAst(program: Program, errors: IErrorReporter) {
val transforms = AstPreprocessor(program, errors)
transforms.visit(this)
var mods = transforms.applyModifications()
while(mods>0)
@@ -83,7 +91,8 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, program: Program,
transforms.applyModifications()
val lit2decl = LiteralsToAutoVars(this)
lit2decl.visit(this)
lit2decl.applyModifications()
if(errors.noErrors())
lit2decl.applyModifications()
}
}
@@ -3,9 +3,8 @@ package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.base.Position
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.FunctionCallExpr
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
@@ -129,7 +128,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
super.visit(string)
}
override fun visit(functionCall: FunctionCall) = visitFunctionCall(functionCall)
override fun visit(functionCallExpr: FunctionCallExpr) = visitFunctionCall(functionCallExpr)
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
private fun visitFunctionCall(call: IFunctionCall) {
@@ -140,12 +139,25 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
errors.err("invalid number of arguments", pos)
}
}
is BuiltinFunctionStatementPlaceholder -> {
is BuiltinFunctionPlaceholder -> {
val func = BuiltinFunctions.getValue(target.name)
if(call.args.size != func.parameters.size) {
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
errors.err("invalid number of arguments", pos)
}
if(func.name=="memory") {
val name = call.args[0] as? StringLiteralValue
if(name!=null) {
val processed = name.value.map {
if(it.isLetterOrDigit())
it
else
'_'
}.joinToString("")
call.args[0] = StringLiteralValue(processed, false, name.position)
call.args[0].linkParents(call as Node)
}
}
}
is Label -> {
if(call.args.isNotEmpty()) {
@@ -154,7 +166,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
}
}
null -> {}
else -> throw FatalAstException("weird call target")
else -> errors.err("cannot call this as a subroutine or function", call.target.position)
}
}
}
@@ -2,21 +2,15 @@ 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
import prog8.ast.statements.VarDecl
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.IErrorReporter
class AstPreprocessor(val program: Program) : AstWalker() {
class AstPreprocessor(val program: Program, val errors: IErrorReporter) : 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
@@ -82,4 +76,13 @@ class AstPreprocessor(val program: Program) : AstWalker() {
}
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
// this has to be done here becuse otherwise the string / range literal values will have been replaced by variables
if(expr.operator=="in") {
val containment = ContainmentCheck(expr.left, expr.right, expr.position)
return listOf(IAstModification.ReplaceNode(expr, containment, parent))
}
return noModifications
}
}
@@ -0,0 +1,144 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.*
internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
// Some more code shuffling to simplify the Ast that the codegenerator has to process.
// Several changes have already been done by the StatementReorderer !
// But the ones here are simpler and are repeated once again after all optimization steps
// have been performed (because those could re-introduce nodes that have to be desugared)
//
// List of modifications:
// - replace 'break' statements by a goto + generated after label.
// - replace while and do-until loops by just jumps.
// - replace peek() and poke() by direct memory accesses.
private var generatedLabelSequenceNumber: Int = 0
private val generatedLabelPrefix = "prog8_label_"
private fun makeLabel(postfix: String, position: Position): Label {
generatedLabelSequenceNumber++
return Label("${generatedLabelPrefix}${generatedLabelSequenceNumber}_$postfix", position)
}
private fun jumpLabel(label: Label): Jump {
val ident = IdentifierReference(listOf(label.name), label.position)
return Jump(null, ident, null, label.position)
}
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
fun jumpAfter(stmt: Statement): Iterable<IAstModification> {
val label = makeLabel("after", breakStmt.position)
return listOf(
IAstModification.ReplaceNode(breakStmt, jumpLabel(label), parent),
IAstModification.InsertAfter(stmt, label, stmt.parent as IStatementContainer)
)
}
var partof = parent
while(true) {
when (partof) {
is Subroutine, is Block, is ParentSentinel -> {
errors.err("break in wrong scope", breakStmt.position)
return noModifications
}
is ForLoop,
is RepeatLoop,
is UntilLoop,
is WhileLoop -> return jumpAfter(partof as Statement)
else -> partof = partof.parent
}
}
}
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
/*
do { STUFF } until CONDITION
===>
_loop:
STUFF
if not CONDITION
goto _loop
*/
val pos = untilLoop.position
val loopLabel = makeLabel("untilloop", pos)
val notCondition = PrefixExpression("not", untilLoop.condition, pos)
val replacement = AnonymousScope(mutableListOf(
loopLabel,
untilLoop.body,
IfElse(notCondition,
AnonymousScope(mutableListOf(jumpLabel(loopLabel)), pos),
AnonymousScope(mutableListOf(), pos),
pos)
), pos)
return listOf(IAstModification.ReplaceNode(untilLoop, replacement, parent))
}
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
/*
while CONDITION { STUFF }
==>
_whileloop:
if NOT CONDITION goto _after
STUFF
goto _whileloop
_after:
*/
val pos = whileLoop.position
val loopLabel = makeLabel("whileloop", pos)
val afterLabel = makeLabel("afterwhile", pos)
val notCondition = PrefixExpression("not", whileLoop.condition, pos)
val replacement = AnonymousScope(mutableListOf(
loopLabel,
IfElse(notCondition,
AnonymousScope(mutableListOf(jumpLabel(afterLabel)), pos),
AnonymousScope(mutableListOf(), pos),
pos),
whileLoop.body,
jumpLabel(loopLabel),
afterLabel
), pos)
return listOf(IAstModification.ReplaceNode(whileLoop, replacement, parent))
}
override fun before(functionCallStatement: FunctionCallStatement, parent: Node) =
before(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
override fun before(functionCallExpr: FunctionCallExpr, parent: Node) =
before(functionCallExpr as IFunctionCall, parent, functionCallExpr.position)
private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
if(functionCall.target.nameInSource==listOf("peek")) {
// peek(a) is synonymous with @(a)
val memread = DirectMemoryRead(functionCall.args.single(), position)
return listOf(IAstModification.ReplaceNode(functionCall as Node, memread, parent))
}
if(functionCall.target.nameInSource==listOf("poke")) {
// poke(a, v) is synonymous with @(a) = v
val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), position)
val assign = Assignment(tgt, functionCall.args[1], position)
return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent))
}
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator=="in") {
println("IN-TEST: $expr\n in: $parent")
}
return noModifications
}
}
@@ -1,10 +1,10 @@
package prog8.compiler.astprocessing
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.ContainmentCheck
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.VarDecl
@@ -16,7 +16,7 @@ import prog8.ast.walk.IAstModification
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
if(string.parent !is VarDecl && string.parent !is WhenChoice && string.parent !is ContainmentCheck) {
// replace the literal string by an identifier reference to the interned string
val scopedName = program.internString(string)
val identifier = IdentifierReference(scopedName, string.position)
@@ -28,26 +28,27 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
val vardecl = array.parent as? VarDecl
if(vardecl!=null) {
// adjust the datatype of the array (to an educated guess)
// adjust the datatype of the array (to an educated guess from the vardecl type)
val arrayDt = array.type
if(arrayDt isnot vardecl.datatype) {
val cast = array.cast(vardecl.datatype)
if (cast != null && cast !== array)
if(cast!=null && cast !== array)
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
}
} else {
if(array.parent is ContainmentCheck)
return noModifications
val arrayDt = array.guessDatatype(program)
if(arrayDt.isKnown) {
// this array literal is part of an expression, turn it into an identifier reference
// turn the array literal it into an identifier reference
val litval2 = array.cast(arrayDt.getOr(DataType.UNDEFINED))
if(litval2!=null) {
if(array.parent !is IStatementContainer)
return noModifications
val vardecl2 = VarDecl.createAuto(litval2)
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
return listOf(
IAstModification.ReplaceNode(array, identifier, parent),
IAstModification.InsertFirst(vardecl2, array.parent as IStatementContainer)
IAstModification.ReplaceNode(array, identifier, parent),
IAstModification.InsertFirst(vardecl2, array.definingScope)
)
}
}
@@ -0,0 +1,247 @@
package prog8.compiler.astprocessing
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
internal class ParentNodeChecker: AstWalker() {
override fun before(addressOf: AddressOf, parent: Node): Iterable<IAstModification> {
if(addressOf.parent!==parent)
throw FatalAstException("parent node mismatch at $addressOf")
return noModifications
}
override fun before(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
if(array.parent!==parent)
throw FatalAstException("parent node mismatch at $array")
return noModifications
}
override fun before(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
if(arrayIndexedExpression.parent!==parent)
throw FatalAstException("parent node mismatch at $arrayIndexedExpression")
return noModifications
}
override fun before(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> {
if(assignTarget.parent!==parent)
throw FatalAstException("parent node mismatch at $assignTarget")
return noModifications
}
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.parent!==parent)
throw FatalAstException("parent node mismatch at $assignment")
return noModifications
}
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
if(block.parent!==parent)
throw FatalAstException("parent node mismatch at $block")
return noModifications
}
override fun before(branch: Branch, parent: Node): Iterable<IAstModification> {
if(branch.parent!==parent)
throw FatalAstException("parent node mismatch at $branch")
return noModifications
}
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
if(breakStmt.parent!==parent)
throw FatalAstException("parent node mismatch at $breakStmt")
return noModifications
}
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.parent!==parent)
throw FatalAstException("parent node mismatch at $decl")
return noModifications
}
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
if(directive.parent!==parent)
throw FatalAstException("parent node mismatch at $directive")
return noModifications
}
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.parent!==parent)
throw FatalAstException("parent node mismatch at $expr")
return noModifications
}
override fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
if(expr.parent!==parent)
throw FatalAstException("parent node mismatch at $expr")
return noModifications
}
override fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
if(forLoop.parent!==parent)
throw FatalAstException("parent node mismatch at $forLoop")
return noModifications
}
override fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
if(repeatLoop.parent!==parent)
throw FatalAstException("parent node mismatch at $repeatLoop")
return noModifications
}
override fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
if(identifier.parent!==parent)
throw FatalAstException("parent node mismatch at $identifier")
return noModifications
}
override fun before(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
if(ifElse.parent!==parent)
throw FatalAstException("parent node mismatch at $ifElse")
return noModifications
}
override fun before(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> {
if(inlineAssembly.parent!==parent)
throw FatalAstException("parent node mismatch at $inlineAssembly")
return noModifications
}
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
if(jump.parent!==parent)
throw FatalAstException("parent node mismatch at $jump")
return noModifications
}
override fun before(gosub: GoSub, parent: Node): Iterable<IAstModification> {
if(gosub.parent!==parent)
throw FatalAstException("parent node mismatch at $gosub")
return noModifications
}
override fun before(label: Label, parent: Node): Iterable<IAstModification> {
if(label.parent!==parent)
throw FatalAstException("parent node mismatch at $label")
return noModifications
}
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
if(memread.parent!==parent)
throw FatalAstException("parent node mismatch at $memread")
return noModifications
}
override fun before(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
if(memwrite.parent!==parent)
throw FatalAstException("parent node mismatch at $memwrite")
return noModifications
}
override fun before(module: Module, parent: Node): Iterable<IAstModification> {
if(module.parent!==parent)
throw FatalAstException("parent node mismatch at $module")
return noModifications
}
override fun before(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> {
if(numLiteral.parent!==parent)
throw FatalAstException("parent node mismatch at $numLiteral")
return noModifications
}
override fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> {
if(postIncrDecr.parent!==parent)
throw FatalAstException("parent node mismatch at $postIncrDecr")
return noModifications
}
override fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> {
if(range.parent!==parent)
throw FatalAstException("parent node mismatch at $range")
return noModifications
}
override fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
if(untilLoop.parent!==parent)
throw FatalAstException("parent node mismatch at $untilLoop")
return noModifications
}
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
if(returnStmt.parent!==parent)
throw FatalAstException("parent node mismatch at $returnStmt")
return noModifications
}
override fun before(char: CharLiteral, parent: Node): Iterable<IAstModification> {
if(char.parent!==parent)
throw FatalAstException("parent node mismatch at $char")
return noModifications
}
override fun before(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
if(string.parent!==parent)
throw FatalAstException("parent node mismatch at $string")
return noModifications
}
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
if(subroutine.parent!==parent)
throw FatalAstException("parent node mismatch at $subroutine")
return noModifications
}
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.parent!==parent)
throw FatalAstException("parent node mismatch at $typecast")
return noModifications
}
override fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> {
if(whenChoice.parent!==parent)
throw FatalAstException("parent node mismatch at $whenChoice")
return noModifications
}
override fun before(whenStmt: When, parent: Node): Iterable<IAstModification> {
if(whenStmt.parent!==parent)
throw FatalAstException("parent node mismatch at $whenStmt")
return noModifications
}
override fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
if(whileLoop.parent!==parent)
throw FatalAstException("parent node mismatch at $whileLoop")
return noModifications
}
override fun before(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
if(functionCallExpr.parent!==parent)
throw FatalAstException("parent node mismatch at $functionCallExpr")
return noModifications
}
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.parent!==parent)
throw FatalAstException("parent node mismatch at $functionCallStatement")
return noModifications
}
override fun before(nop: Nop, parent: Node): Iterable<IAstModification> {
if(nop.parent!==parent)
throw FatalAstException("parent node mismatch at $nop")
return noModifications
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
if(scope.parent!==parent)
throw FatalAstException("parent node mismatch at $scope")
return noModifications
}
}
@@ -10,7 +10,6 @@ import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.IErrorReporter
internal class StatementReorderer(val program: Program,
val errors: IErrorReporter,
private val options: CompilationOptions) : AstWalker() {
@@ -60,6 +59,14 @@ internal class StatementReorderer(val program: Program,
return noModifications
}
val nextStmt = decl.nextSibling()
val nextAssign = nextStmt as? Assignment
if(nextAssign!=null && !nextAssign.isAugmentable) {
val target = nextAssign.target.identifier?.targetStatement(program)
if(target === decl) {
// an initializer assignment for a vardecl is already here
return noModifications
}
}
val nextFor = nextStmt as? ForLoop
val hasNextForWithThisLoopvar = nextFor?.loopVar?.nameInSource==listOf(decl.name)
if (!hasNextForWithThisLoopvar) {
@@ -170,7 +177,7 @@ internal class StatementReorderer(val program: Program,
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
// (this should be done by the ExpressionSimplifier when optimizing is enabled,
// but the current assembly code generator for IF statements now also depends on it, so we do it here regardless of optimization.)
if (expr.left.constValue(program) != null && expr.operator in associativeOperators && expr.right.constValue(program) == null)
if (expr.left.constValue(program) != null && expr.operator in AssociativeOperators && expr.right.constValue(program) == null)
return listOf(IAstModification.SwapOperands(expr))
// when using a simple bit shift and assigning it to a variable of a different type,
@@ -203,7 +210,7 @@ internal class StatementReorderer(val program: Program,
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
is BuiltinFunctionStatementPlaceholder -> {
is BuiltinFunctionPlaceholder -> {
val func = BuiltinFunctions.getValue(callee.name)
val paramTypes = func.parameters[argnum].possibleDatatypes
for(type in paramTypes) {
@@ -220,7 +227,7 @@ internal class StatementReorderer(val program: Program,
else -> return noModifications
}
}
else if(expr.operator in logicalOperators) {
else if(expr.operator in LogicalOperators) {
// make sure that logical expressions like "var and other-logical-expression
// is rewritten as "var!=0 and other-logical-expression", to avoid bitwise boolean and
// generating the wrong results later
@@ -229,9 +236,9 @@ internal class StatementReorderer(val program: Program,
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))
if(expr is BinaryExpression && expr.operator in (LogicalOperators + ComparisonOperators))
return true
if(expr is PrefixExpression && expr.operator in logicalOperators)
if(expr is PrefixExpression && expr.operator in LogicalOperators)
return true
return false
}
@@ -255,19 +262,19 @@ internal class StatementReorderer(val program: Program,
return noModifications
}
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
val lastChoiceValues = whenStatement.choices.lastOrNull()?.values
override fun after(whenStmt: When, parent: Node): Iterable<IAstModification> {
val lastChoiceValues = whenStmt.choices.lastOrNull()?.values
if(lastChoiceValues?.isNotEmpty()==true) {
val elseChoice = whenStatement.choices.indexOfFirst { it.values==null || it.values?.isEmpty()==true }
val elseChoice = whenStmt.choices.indexOfFirst { it.values==null || it.values?.isEmpty()==true }
if(elseChoice>=0)
errors.err("else choice must be the last one", whenStatement.choices[elseChoice].position)
errors.err("else choice must be the last one", whenStmt.choices[elseChoice].position)
}
val choices = whenStatement.choiceValues(program).sortedBy {
val choices = whenStmt.choiceValues(program).sortedBy {
it.first?.first() ?: Int.MAX_VALUE
}
whenStatement.choices.clear()
choices.mapTo(whenStatement.choices) { it.second }
whenStmt.choices.clear()
choices.mapTo(whenStmt.choices) { it.second }
return noModifications
}
@@ -295,7 +302,7 @@ internal class StatementReorderer(val program: Program,
return noModifications
}
if(binExpr.operator in associativeOperators) {
if(binExpr.operator in AssociativeOperators) {
if (binExpr.right isSameAs assignment.target) {
// A = v <associative-operator> A ==> A = A <associative-operator> v
return listOf(IAstModification.SwapOperands(binExpr))
@@ -392,6 +399,19 @@ internal class StatementReorderer(val program: Program,
return listOf(IAstModification.ReplaceNode(call, GoSub(null, call.target, null, call.position), parent))
}
if(function.parameters.size==1) {
if(function.parameters[0].type in IntegerDatatypes) {
// optimization: 1 integer param is passed via register(s) directly, not by assignment to param variable
return noModifications
}
}
else if(function.parameters.size==2) {
if(function.parameters[0].type in ByteDatatypes && function.parameters[1].type in ByteDatatypes) {
// optimization: 2 simple byte param is passed via 2 registers directly, not by assignment to param variables
return noModifications
}
}
val assignParams =
function.parameters.zip(call.args).map {
var argumentValue = it.second
@@ -428,8 +448,6 @@ internal class StatementReorderer(val program: Program,
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()) {
@@ -9,10 +9,11 @@ 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.IErrorReporter
class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalker() {
class TypecastsAdder(val program: Program, val options: CompilationOptions, val errors: IErrorReporter) : AstWalker() {
/*
* Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
* (this includes function call arguments)
@@ -48,7 +49,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
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) {
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
@@ -134,8 +135,8 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
return afterFunctionCallArgs(functionCallStatement)
}
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
return afterFunctionCallArgs(functionCall)
override fun after(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
return afterFunctionCallArgs(functionCallExpr)
}
private fun afterFunctionCallArgs(call: IFunctionCall): Iterable<IAstModification> {
@@ -179,7 +180,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
}
}
}
is BuiltinFunctionStatementPlaceholder -> {
is BuiltinFunctionPlaceholder -> {
val func = BuiltinFunctions.getValue(sub.name)
func.parameters.zip(call.args).forEachIndexed { index, pair ->
val argItype = pair.second.inferType(program)
@@ -220,7 +221,10 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// warn about any implicit type casts to Float, because that may not be intended
if(typecast.implicit && typecast.type.oneOf(DataType.FLOAT, DataType.ARRAY_F)) {
errors.warn("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position)
if(options.floats)
errors.warn("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position)
else
errors.err("integer implicitly converted to float but floating point is not enabled via options", typecast.position)
}
return noModifications
}
@@ -1,11 +1,11 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ArrayDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.Position
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
@@ -15,18 +15,18 @@ import prog8.compilerinterface.IErrorReporter
internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() {
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent as IStatementContainer))
override fun before(nop: Nop, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nop, parent as IStatementContainer))
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is IStatementContainer)
listOf(ScopeFlatten(scope, parent as IStatementContainer))
else
noModifications
}
class ScopeFlatten(val scope: AnonymousScope, val into: IStatementContainer) : IAstModification {
private class ScopeFlatten(val scope: AnonymousScope, val into: IStatementContainer) : IAstModification {
override fun perform() {
val idx = into.statements.indexOf(scope)
if(idx>=0) {
@@ -37,33 +37,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
}
}
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return before(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
}
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
return before(functionCall as IFunctionCall, parent, functionCall.position)
}
private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
if(functionCall.target.nameInSource==listOf("peek")) {
// peek(a) is synonymous with @(a)
val memread = DirectMemoryRead(functionCall.args.single(), position)
return listOf(IAstModification.ReplaceNode(functionCall as Node, memread, parent))
}
if(functionCall.target.nameInSource==listOf("poke")) {
// poke(a, v) is synonymous with @(a) = v
val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), position)
val assign = Assignment(tgt, functionCall.args[1], position)
return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent))
}
return noModifications
}
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.parent!==parent)
throw FatalAstException("parent node mismatch at $typecast")
if(typecast.expression is NumericLiteralValue) {
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
if(value.isValid)
@@ -85,16 +59,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
if(subroutine.parent!==parent)
throw FatalAstException("parent node mismatch at $subroutine")
return noModifications
}
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)
@@ -104,33 +69,134 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
return noModifications
}
override fun after(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> {
if(assignTarget.parent!==parent)
throw FatalAstException("parent node mismatch at $assignTarget")
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator=="+") {
// +X --> X
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))
}
if(expr.operator=="not") {
val nestedPrefix = expr.expression as? PrefixExpression
if(nestedPrefix!=null && nestedPrefix.operator=="not") {
// NOT NOT X --> X
return listOf(IAstModification.ReplaceNode(expr, nestedPrefix.expression, parent))
}
val comparison = expr.expression as? BinaryExpression
if (comparison != null) {
// NOT COMPARISON ==> inverted COMPARISON
val invertedOperator = invertedComparisonOperator(comparison.operator)
if (invertedOperator != null) {
comparison.operator = invertedOperator
return listOf(IAstModification.ReplaceNode(expr, comparison, parent))
}
}
}
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.parent!==parent)
throw FatalAstException("parent node mismatch at $decl")
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator == "or") {
val leftBinExpr = expr.left as? BinaryExpression
val rightBinExpr = expr.right as? BinaryExpression
if(leftBinExpr!=null && leftBinExpr.operator=="==" && rightBinExpr!=null && rightBinExpr.operator=="==") {
if(leftBinExpr.right is NumericLiteralValue && rightBinExpr.right is NumericLiteralValue) {
if(leftBinExpr.left isSameAs rightBinExpr.left)
errors.warn("consider using 'in' or 'when' to test for multiple values", expr.position)
}
}
}
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
if(scope.parent!==parent)
throw FatalAstException("parent node mismatch at $scope")
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator in ComparisonOperators) {
val leftConstVal = expr.left.constValue(program)
val rightConstVal = expr.right.constValue(program)
// make sure the constant value is on the right of the comparison expression
if(rightConstVal==null && leftConstVal!=null) {
val newOperator =
when(expr.operator) {
"<" -> ">"
"<=" -> ">="
">" -> "<"
">=" -> "<="
else -> expr.operator
}
val replacement = BinaryExpression(expr.right, newOperator, expr.left, expr.position)
return listOf(IAstModification.ReplaceNode(expr, replacement, parent))
}
}
return noModifications
}
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
if(returnStmt.parent!==parent)
throw FatalAstException("parent node mismatch at $returnStmt")
return noModifications
}
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
// replace trivial containment checks with just false or a single comparison
fun replaceWithEquals(value: NumericLiteralValue): Iterable<IAstModification> {
errors.warn("containment could be written as just a single comparison", containment.position)
val equals = BinaryExpression(containment.element, "==", value, containment.position)
return listOf(IAstModification.ReplaceNode(containment, equals, parent))
}
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
if(identifier.parent!==parent)
throw FatalAstException("parent node mismatch at $identifier")
fun replaceWithFalse(): Iterable<IAstModification> {
errors.warn("condition is always false", containment.position)
return listOf(IAstModification.ReplaceNode(containment, NumericLiteralValue.fromBoolean(false, containment.position), parent))
}
fun checkArray(array: Array<Expression>): Iterable<IAstModification> {
if(array.isEmpty())
return replaceWithFalse()
if(array.size==1) {
val constVal = array[0].constValue(program)
if(constVal!=null)
return replaceWithEquals(constVal)
}
return noModifications
}
fun checkString(stringVal: StringLiteralValue): Iterable<IAstModification> {
if(stringVal.value.isEmpty())
return replaceWithFalse()
if(stringVal.value.length==1) {
val string = program.encoding.encodeString(stringVal.value, stringVal.altEncoding)
return replaceWithEquals(NumericLiteralValue(DataType.UBYTE, string[0].toDouble(), stringVal.position))
}
return noModifications
}
when(containment.iterable) {
is ArrayLiteralValue -> {
val array = (containment.iterable as ArrayLiteralValue).value
return checkArray(array)
}
is IdentifierReference -> {
val variable = (containment.iterable as IdentifierReference).targetVarDecl(program)!!
when(variable.datatype) {
DataType.STR -> {
val stringVal = (variable.value as StringLiteralValue)
return checkString(stringVal)
}
in ArrayDatatypes -> {
val array = (variable.value as ArrayLiteralValue).value
return checkArray(array)
}
else -> {}
}
}
is RangeExpr -> {
val constValues = (containment.iterable as RangeExpr).toConstantIntegerRange()
if(constValues!=null) {
if (constValues.isEmpty())
return replaceWithFalse()
if (constValues.count()==1)
return replaceWithEquals(NumericLiteralValue.optimalNumeric(constValues.first, containment.position))
}
}
is StringLiteralValue -> {
val stringVal = containment.iterable as StringLiteralValue
return checkString(stringVal)
}
else -> {}
}
return noModifications
}
}
@@ -4,7 +4,7 @@ import prog8.ast.IFunctionCall
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.FunctionCallExpr
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
@@ -13,8 +13,8 @@ import prog8.compilerinterface.InternalCompilerException
internal class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
override fun visit(functionCall: FunctionCall) {
val error = checkTypes(functionCall as IFunctionCall, program)
override fun visit(functionCallExpr: FunctionCallExpr) {
val error = checkTypes(functionCallExpr as IFunctionCall, program)
if(error!=null)
throw InternalCompilerException(error)
}
@@ -76,7 +76,7 @@ internal class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
}
}
}
else if (target is BuiltinFunctionStatementPlaceholder) {
else if (target is BuiltinFunctionPlaceholder) {
val func = BuiltinFunctions.getValue(target.name)
if(call.args.size != func.parameters.size)
return "invalid number of arguments"
-1
View File
@@ -55,7 +55,6 @@ class TestModuleImporter: FunSpec({
error1.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
}
program.modules.size shouldBe 1
val error2 = importer.importModule(srcPathAbs).getErrorOrElse { fail("should have import error") }
withClue(".file should be normalized") {
"${error2.file}" shouldBe "${error2.file.normalize()}"
+33
View File
@@ -0,0 +1,33 @@
package prog8tests
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import prog8.codegen.target.C64Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
class TestAstChecks: FunSpec({
test("conditional expression w/float works even without tempvar to split it") {
val text = """
%import floats
main {
sub start() {
uword xx
if xx+99.99 == xx+1.234 {
xx++
}
}
}
"""
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
compileText(C64Target, true, text, writeAssembly = true, errors=errors).assertSuccess()
errors.errors.size shouldBe 0
errors.warnings.size shouldBe 2
errors.warnings[0] shouldContain "converted to float"
errors.warnings[1] shouldContain "converted to float"
}
})
+1 -1
View File
@@ -7,7 +7,7 @@ import io.kotest.matchers.maps.shouldNotContainKey
import io.kotest.matchers.shouldBe
import prog8.ast.statements.Block
import prog8.ast.statements.Subroutine
import prog8.compiler.target.C64Target
import prog8.codegen.target.C64Target
import prog8.compilerinterface.CallGraph
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
+1 -1
View File
@@ -11,7 +11,7 @@ import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.Assignment
import prog8.compiler.target.Cx16Target
import prog8.codegen.target.Cx16Target
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
+4 -2
View File
@@ -4,8 +4,8 @@ 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
import prog8.codegen.target.C64Target
import prog8.codegen.target.Cx16Target
import prog8.compilerinterface.ICompilationTarget
import prog8tests.helpers.*
import prog8tests.helpers.assertSuccess
@@ -27,9 +27,11 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
filepath,
optimize,
optimizeFloatExpressions = true,
dontReinitGlobals = false,
writeAssembly = true,
slowCodegenWarnings = false,
quietAssembler = true,
asmListfile = false,
compilationTarget = target.name,
outputDir = outputDir
)
@@ -9,11 +9,8 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Label
import prog8.compiler.target.Cx16Target
import prog8.codegen.target.Cx16Target
import prog8tests.helpers.*
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileFile
import kotlin.io.path.name
+131 -4
View File
@@ -10,10 +10,8 @@ import prog8.ast.base.Position
import prog8.ast.expressions.*
import prog8.ast.statements.ForLoop
import prog8.ast.statements.VarDecl
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compilerinterface.size
import prog8.compilerinterface.toConstantIntegerRange
import prog8.codegen.target.C64Target
import prog8.codegen.target.Cx16Target
import prog8tests.helpers.*
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure
@@ -301,4 +299,133 @@ class TestCompilerOnRanges: FunSpec({
forloop.iterable shouldBe instanceOf<RangeExpr>()
(forloop.iterable as RangeExpr).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY)
}
test("for statement on all possible iterable expressions") {
compileText(C64Target, false, """
main {
sub start() {
ubyte xx
uword ww
str name = "irmen"
ubyte[] values = [1,2,3,4,5,6,7]
uword[] wvalues = [1000,2000,3000]
for xx in name {
xx++
}
for xx in values {
xx++
}
for xx in 10 to 20 step 3 {
xx++
}
for xx in "abcdef" {
xx++
}
for xx in [2,4,6,8] {
xx++
}
for ww in [9999,8888,7777] {
xx++
}
for ww in wvalues {
xx++
}
}
}""", writeAssembly = true).assertSuccess()
}
test("if containment check on all possible iterable expressions") {
compileText(C64Target, false, """
main {
sub start() {
ubyte xx
uword ww
str name = "irmen"
ubyte[] values = [1,2,3,4,5,6,7]
uword[] wvalues = [1000,2000,3000]
if 'm' in name {
xx++
}
if 5 in values {
xx++
}
if 16 in 10 to 20 step 3 {
xx++
}
if 'b' in "abcdef" {
xx++
}
if 8 in [2,4,6,8] {
xx++
}
if xx in name {
xx++
}
if xx in values {
xx++
}
if xx in 10 to 20 step 3 {
xx++
}
if xx in "abcdef" {
xx++
}
if xx in [2,4,6,8] {
xx++
}
if ww in [9999,8888,7777] {
xx++
}
if ww in wvalues {
xx++
}
}
}""", writeAssembly = true).assertSuccess()
}
test("containment check in expressions") {
compileText(C64Target, false, """
main {
sub start() {
ubyte xx
uword ww
str name = "irmen"
ubyte[] values = [1,2,3,4,5,6,7]
uword[] wvalues = [1000,2000,3000]
xx = 'm' in name
xx = 5 in values
xx = 16 in 10 to 20 step 3
xx = 'b' in "abcdef"
xx = 8 in [2,4,6,8]
xx = xx in name
xx = xx in values
xx = xx in 10 to 20 step 3
xx = xx in "abcdef"
xx = xx in [2,4,6,8]
xx = ww in [9000,8000,7000]
xx = ww in wvalues
}
}""", writeAssembly = true).assertSuccess()
}
})
+3 -1
View File
@@ -4,7 +4,7 @@ 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 prog8.codegen.target.Cx16Target
import prog8tests.helpers.*
import prog8tests.helpers.assertSuccess
import java.nio.file.Path
@@ -41,9 +41,11 @@ class TestCompilerOptionSourcedirs: FunSpec({
filepath = filePath,
optimize = false,
optimizeFloatExpressions = false,
dontReinitGlobals = false,
writeAssembly = true,
slowCodegenWarnings = false,
quietAssembler = true,
asmListfile = false,
compilationTarget = Cx16Target.name,
sourceDirs,
outputDir
@@ -7,7 +7,7 @@ import io.kotest.matchers.string.shouldStartWith
import prog8.ast.internedStringsModuleName
import prog8.compiler.determineCompilationOptions
import prog8.compiler.parseImports
import prog8.compiler.target.C64Target
import prog8.codegen.target.C64Target
import prog8.compilerinterface.ZeropageType
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertSuccess
+14 -1
View File
@@ -13,12 +13,14 @@ 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.codegen.target.C64Target
import prog8.compilerinterface.isIOAddress
import prog8.parser.SourceCode
import prog8tests.helpers.*
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
import prog8tests.helpers.compileText
class TestMemory: FunSpec({
@@ -220,4 +222,15 @@ class TestMemory: FunSpec({
.addModule(module)
target.isIOAddress(C64Target.machine) shouldBe true
}
test("memory() with spaces in name works") {
val result = compileText(C64Target, false, """
main {
sub start() {
uword @shared mem = memory("a b c", 100)
}
}
""", writeAssembly = true).assertSuccess()
}
})
+48 -1
View File
@@ -4,9 +4,15 @@ import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.doubles.plusOrMinus
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import prog8.ast.toHex
import prog8.compiler.target.cbm.Mflpt5
import prog8.codegen.target.C64Target
import prog8.codegen.target.cbm.Mflpt5
import prog8.compilerinterface.InternalCompilerException
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
class TestNumbers: FunSpec({
@@ -105,4 +111,45 @@ class TestNumbers: FunSpec({
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)
}
test("implicit float conversion warning if enabled") {
val src="""
%option enable_floats
main {
sub start() {
uword xx = 10
if xx+99 == 1.23456
xx++
if xx+99 == 1234567
xx++
}
}
"""
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
compileText(C64Target, true, src, writeAssembly = false, errors=errors).assertSuccess()
errors.errors.size shouldBe 0
errors.warnings.size shouldBe 2
errors.warnings[0] shouldContain "converted to float"
errors.warnings[1] shouldContain "converted to float"
}
test("implicit float conversion error if not enabled") {
val src="""
main {
sub start() {
uword xx = 10
if xx+99 == 1.23456
xx++
if xx+99 == 1234567
xx++
}
}
"""
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
compileText(C64Target, true, src, writeAssembly = false, errors=errors).assertFailure()
errors.errors.size shouldBe 2
errors.warnings.size shouldBe 0
errors.errors[0] shouldContain "converted to float"
errors.errors[1] shouldContain "converted to float"
}
})
+7 -6
View File
@@ -16,7 +16,7 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.compiler.printProgram
import prog8.compiler.target.C64Target
import prog8.codegen.target.C64Target
import prog8.compilerinterface.*
import prog8tests.helpers.*
import prog8tests.helpers.DummyFunctions
@@ -278,7 +278,7 @@ class TestOptimization: FunSpec({
wwAssign.target.identifier?.nameInSource shouldBe listOf("ww")
expr.type shouldBe DataType.UWORD
expr.expression.inferType(result.program).istype(DataType.UBYTE) shouldBe true
expr.expression.inferType(result.program) istype DataType.UBYTE shouldBe true
}
test("intermediate assignment steps have correct types for codegen phase (BeforeAsmGenerationAstChanger)") {
@@ -444,7 +444,7 @@ class TestOptimization: FunSpec({
}"""
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
result.program.entrypoint.statements.size shouldBe 3
val ifstmt = result.program.entrypoint.statements[0] as IfStatement
val ifstmt = result.program.entrypoint.statements[0] as IfElse
ifstmt.truepart.statements.size shouldBe 1
(ifstmt.truepart.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
val func2 = result.program.entrypoint.statements[2] as Subroutine
@@ -633,14 +633,13 @@ class TestOptimization: FunSpec({
uword zz
zz = 60
ubyte xx
xx = 0
xx = sin8u(xx)
xx += 6
*/
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 8
stmts.size shouldBe 7
stmts.filterIsInstance<VarDecl>().size shouldBe 3
stmts.filterIsInstance<Assignment>().size shouldBe 5
stmts.filterIsInstance<Assignment>().size shouldBe 4
}
test("only substitue assignments with 0 after a =0 initializer if it is the same variable") {
@@ -666,6 +665,8 @@ class TestOptimization: FunSpec({
yy = 0
xx += 10
*/
printProgram(result.program)
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 7
stmts.filterIsInstance<VarDecl>().size shouldBe 2
+1 -1
View File
@@ -6,7 +6,7 @@ import com.github.michaelbull.result.getOrElse
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.compiler.target.cbm.Petscii
import prog8.codegen.target.cbm.Petscii
class TestPetscii: FunSpec({
+1 -1
View File
@@ -10,7 +10,7 @@ import prog8.ast.GlobalNamespace
import prog8.ast.base.ParentSentinel
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.*
import prog8.compiler.target.C64Target
import prog8.codegen.target.C64Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
+7 -7
View File
@@ -10,7 +10,7 @@ import prog8.ast.base.DataType
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.printProgram
import prog8.compiler.target.C64Target
import prog8.codegen.target.C64Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
@@ -206,14 +206,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 11 // rr paramdecl, xx, xx assign, yy decl, yy init 0, yy assign, other, other assign 0, zz, zz assign, return
thing.statements.size shouldBe 10 // rr paramdecl, xx, xx assign, yy decl, 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[5] as Assignment
val assignZZ = thing.statements[9] as Assignment
val assignYY = thing.statements[4] as Assignment
val assignZZ = thing.statements[8] as Assignment
assignXX.target.identifier!!.nameInSource shouldBe listOf("xx")
assignYY.target.identifier!!.nameInSource shouldBe listOf("yy")
assignZZ.target.identifier!!.nameInSource shouldBe listOf("zz")
@@ -324,9 +324,9 @@ class TestSubroutines: FunSpec({
val text = """
main {
sub start() {
func(1)
func(1, 2, 3)
sub func(ubyte a) {
sub func(ubyte a, ubyte b, ubyte c) {
a++
}
}
@@ -337,6 +337,6 @@ class TestSubroutines: FunSpec({
stmts.last() shouldBe instanceOf<Subroutine>()
stmts.dropLast(1).last() shouldBe instanceOf<Return>() // this prevents the fallthrough
stmts.dropLast(2).last() shouldBe instanceOf<Jump>()
stmts.dropLast(2).last() shouldBe instanceOf<GoSub>()
}
})
+12 -40
View File
@@ -11,10 +11,10 @@ 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.C64Zeropage
import prog8.compiler.target.cx16.CX16Zeropage
import prog8.codegen.target.C64Target
import prog8.codegen.target.Cx16Target
import prog8.codegen.target.c64.C64Zeropage
import prog8.codegen.target.cx16.CX16Zeropage
import prog8.compilerinterface.*
import prog8tests.helpers.ErrorReporterForTests
@@ -143,31 +143,16 @@ class TestC64Zeropage: FunSpec({
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
zp3.availableBytes() shouldBe 125
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
zp4.availableBytes() shouldBe 238
zp4.availableBytes() shouldBe 239
zp4.allocate("test", DataType.UBYTE, null, errors)
zp4.availableBytes() shouldBe 237
zp4.availableBytes() shouldBe 238
zp4.allocate("test2", DataType.UBYTE, null, errors)
zp4.availableBytes() shouldBe 236
}
test("testFreeSpacesWords") {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
zp1.availableWords() shouldBe 6
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
zp2.availableWords() shouldBe 38
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
zp3.availableWords() shouldBe 57
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
zp4.availableWords() shouldBe 116
zp4.allocate("test", DataType.UWORD, null, errors)
zp4.availableWords() shouldBe 115
zp4.allocate("test2", DataType.UWORD, null, errors)
zp4.availableWords() shouldBe 114
zp4.availableBytes() shouldBe 237
}
test("testReservedSpace") {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
zp1.availableBytes() shouldBe 238
zp1.availableBytes() shouldBe 239
50u shouldBeIn zp1.free
100u shouldBeIn zp1.free
49u shouldBeIn zp1.free
@@ -214,7 +199,7 @@ class TestC64Zeropage: FunSpec({
test("testFullAllocation") {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
zp.availableBytes() shouldBe 238
zp.availableBytes() shouldBe 239
zp.hasByteAvailable() shouldBe true
zp.hasWordAvailable() shouldBe true
val loc = zp.allocate("", DataType.UWORD, null, errors)
@@ -222,17 +207,17 @@ class TestC64Zeropage: FunSpec({
loc shouldNotBeIn zp.free
val num = zp.availableBytes() / 2
for(i in 0..num-4) {
for(i in 0..num-3) {
zp.allocate("", DataType.UWORD, null, errors)
}
zp.availableBytes() shouldBe 6
zp.availableBytes() shouldBe 5
shouldThrow<ZeropageDepletedError> {
// can't allocate because no more sequential bytes, only fragmented
zp.allocate("", DataType.UWORD, null, errors)
}
for(i in 0..5) {
for(i in 0..4) {
zp.allocate("", DataType.UBYTE, null, errors)
}
@@ -295,19 +280,6 @@ class TestCx16Zeropage: FunSpec({
zp3.availableBytes() shouldBe 214
}
test("testFreeSpacesWords") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
zp1.availableWords() shouldBe 108
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target))
zp2.availableWords() shouldBe 87
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
zp3.availableWords() shouldBe 44
zp3.allocate("test", DataType.UWORD, null, errors)
zp3.availableWords() shouldBe 43
zp3.allocate("test2", DataType.UWORD, null, errors)
zp3.availableWords() shouldBe 42
}
test("testReservedSpace") {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
zp1.availableBytes() shouldBe 216
+75
View File
@@ -0,0 +1,75 @@
%import textio
%zeropage basicsafe
%option no_sysinit
main {
sub start() {
uword amount
uword zero = 0
uword one = 1
uword hundred = 100
uword two55 = 255
uword two56 = 256
uword two57 = 257
uword thousand = 1000
uword maximum = 65535
txt.print("expected:\n 0, 1, 100, 255, 256, 257, 1000, 65535\n\n")
repeat zero {
amount++
}
txt.print_uw(amount)
txt.nl()
amount=0
repeat one {
amount++
}
txt.print_uw(amount)
txt.nl()
amount=0
repeat hundred {
amount++
}
txt.print_uw(amount)
txt.nl()
amount=0
repeat two55 {
amount++
}
txt.print_uw(amount)
txt.nl()
amount=0
repeat two56 {
amount++
}
txt.print_uw(amount)
txt.nl()
amount=0
repeat two57 {
amount++
}
txt.print_uw(amount)
txt.nl()
amount=0
repeat thousand {
amount++
}
txt.print_uw(amount)
txt.nl()
amount=0
repeat maximum {
amount++
}
txt.print_uw(amount)
txt.nl()
}
}
+3 -4
View File
@@ -19,9 +19,8 @@ import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.printProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.cbm.Petscii
import prog8.codegen.target.C64Target
import prog8.codegen.target.cbm.Petscii
import prog8.parser.ParseError
import prog8.parser.Prog8Parser.parseModule
import prog8.parser.SourceCode
@@ -334,7 +333,7 @@ class TestProg8Parser: FunSpec( {
assertPositionOf(rhsFoo, mpf, 4, 21, 22)
val declBar = startSub.statements.filterIsInstance<VarDecl>()[1]
assertPositionOf(declBar, mpf, 5, 9, 13)
val whenStmt = startSub.statements.filterIsInstance<WhenStatement>()[0]
val whenStmt = startSub.statements.filterIsInstance<When>()[0]
assertPositionOf(whenStmt, mpf, 6, 9, 12)
assertPositionOf(whenStmt.choices[0], mpf, 7, 13, 14)
assertPositionOf(whenStmt.choices[1], mpf, 8, 13, 14)
@@ -10,9 +10,9 @@ import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.c64.C64Zeropage
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.codegen.target.C64Target
import prog8.codegen.target.c64.C64Zeropage
import prog8.codegen.target.cpu6502.codegen.AsmGen
import prog8.compilerinterface.*
import prog8.parser.SourceCode
import prog8tests.helpers.DummyFunctions
@@ -21,7 +21,7 @@ import prog8tests.helpers.DummyStringEncoder
import prog8tests.helpers.ErrorReporterForTests
import java.nio.file.Path
class AsmGenSymbolsTests: StringSpec({
class TestAsmGenSymbols: StringSpec({
fun createTestProgram(): Program {
/*
main {
@@ -0,0 +1,32 @@
package prog8tests.codegeneration
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import prog8.codegen.target.C64Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
class TestVariables: FunSpec({
test("shared variables without refs not removed for inlined asm") {
val text = """
main {
sub start() {
ubyte[] @shared arrayvar = [1,2,3,4]
str @shared stringvar = "test"
ubyte @shared bytevar = 0
%asm {{
lda arrayvar
lda stringvar
lda bytevar
}}
}
}
"""
compileText(C64Target, true, text, writeAssembly = true).assertSuccess()
}
})
@@ -3,7 +3,7 @@ package prog8tests.helpers
import prog8.ast.base.Position
import prog8.compilerinterface.IErrorReporter
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true): IErrorReporter {
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false): IErrorReporter {
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()
@@ -23,6 +23,12 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
if(throwExceptionAtReportIfErrors)
finalizeNumErrors(errors.size, warnings.size)
if(!keepMessagesAfterReporting) {
clear()
}
}
fun clear() {
errors.clear()
warnings.clear()
}
+5 -3
View File
@@ -6,9 +6,9 @@ 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.C64Zeropage
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.codegen.target.C64Target
import prog8.codegen.target.c64.C64Zeropage
import prog8.codegen.target.cpu6502.codegen.AsmGen
import prog8.compilerinterface.*
import java.nio.file.Path
import kotlin.io.path.name
@@ -48,9 +48,11 @@ internal fun compileFile(
filepath,
optimize,
optimizeFloatExpressions = optFloatExpr,
dontReinitGlobals = false,
writeAssembly = writeAssembly,
slowCodegenWarnings = false,
quietAssembler = true,
asmListfile = false,
platform.name,
outputDir = outputDir,
errors = errors ?: ErrorReporterForTests()
@@ -48,6 +48,12 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
outputln("}\n")
}
override fun visit(containment: ContainmentCheck) {
containment.element.accept(this)
output(" in ")
containment.iterable.accept(this)
}
override fun visit(expr: PrefixExpression) {
if(expr.operator.any { it.isLetter() })
output(" ${expr.operator} ")
@@ -195,8 +201,8 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
}
override fun visit(functionCall: FunctionCall) {
printout(functionCall as IFunctionCall)
override fun visit(functionCallExpr: FunctionCallExpr) {
printout(functionCallExpr as IFunctionCall)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
@@ -219,10 +225,7 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
override fun visit(jump: Jump) {
if(jump.isGosub)
output("gosub ")
else
output("goto ")
output("goto ")
when {
jump.address!=null -> output(jump.address.toHex())
jump.generatedLabel!=null -> output(jump.generatedLabel)
@@ -230,23 +233,32 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
}
override fun visit(ifStatement: IfStatement) {
output("if ")
ifStatement.condition.accept(this)
output(" ")
ifStatement.truepart.accept(this)
if(ifStatement.elsepart.statements.isNotEmpty()) {
output(" else ")
ifStatement.elsepart.accept(this)
override fun visit(gosub: GoSub) {
output("gosub ")
when {
gosub.address!=null -> output(gosub.address.toHex())
gosub.generatedLabel!=null -> output(gosub.generatedLabel)
gosub.identifier!=null -> gosub.identifier.accept(this)
}
}
override fun visit(branchStatement: BranchStatement) {
output("if_${branchStatement.condition.toString().lowercase()} ")
branchStatement.truepart.accept(this)
if(branchStatement.elsepart.statements.isNotEmpty()) {
override fun visit(ifElse: IfElse) {
output("if ")
ifElse.condition.accept(this)
output(" ")
ifElse.truepart.accept(this)
if(ifElse.elsepart.statements.isNotEmpty()) {
output(" else ")
branchStatement.elsepart.accept(this)
ifElse.elsepart.accept(this)
}
}
override fun visit(branch: Branch) {
output("if_${branch.condition.toString().lowercase()} ")
branch.truepart.accept(this)
if(branch.elsepart.statements.isNotEmpty()) {
output(" else ")
branch.elsepart.accept(this)
}
}
@@ -310,7 +322,7 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null && binExpr.left isSameAs assignment.target
&& binExpr.operator !in arrayOf("and", "or", "xor")
&& binExpr.operator !in comparisonOperators) {
&& binExpr.operator !in ComparisonOperators) {
// we only support the inplace assignments of the form A = A <operator> <value>
assignment.target.accept(this)
output(" ${binExpr.operator}= ")
@@ -416,12 +428,12 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
outputlni("}}")
}
override fun visit(whenStatement: WhenStatement) {
override fun visit(whenStmt: When) {
output("when ")
whenStatement.condition.accept(this)
whenStmt.condition.accept(this)
outputln(" {")
scopelevel++
whenStatement.choices.forEach { it.accept(this) }
whenStmt.choices.forEach { it.accept(this) }
scopelevel--
outputlni("}")
}
+7 -7
View File
@@ -39,14 +39,14 @@ interface IStatementContainer {
when(it) {
is Label -> result.add(it)
is IStatementContainer -> find(it)
is IfStatement -> {
is IfElse -> {
find(it.truepart)
find(it.elsepart)
}
is UntilLoop -> find(it.body)
is RepeatLoop -> find(it.body)
is WhileLoop -> find(it.body)
is WhenStatement -> it.choices.forEach { choice->find(choice.statements) }
is When -> it.choices.forEach { choice->find(choice.statements) }
else -> { /* do nothing */ }
}
}
@@ -87,12 +87,12 @@ interface IStatementContainer {
if(found!=null)
return found
}
is IfStatement -> {
is IfElse -> {
val found = stmt.truepart.searchSymbol(name) ?: stmt.elsepart.searchSymbol(name)
if(found!=null)
return found
}
is BranchStatement -> {
is Branch -> {
val found = stmt.truepart.searchSymbol(name) ?: stmt.elsepart.searchSymbol(name)
if(found!=null)
return found
@@ -117,7 +117,7 @@ interface IStatementContainer {
if(found!=null)
return found
}
is WhenStatement -> {
is When -> {
stmt.choices.forEach {
val found = it.statements.searchSymbol(name)
if(found!=null)
@@ -244,8 +244,8 @@ interface Node {
open class Module(final override var statements: MutableList<Statement>,
final override val position: Position,
val source: SourceCode) : Node, INameScope {
final override val position: Position,
val source: SourceCode) : Node, INameScope {
override lateinit var parent: Node
lateinit var program: Program
+2 -1
View File
@@ -4,6 +4,7 @@ import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.ContainmentCheck
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.Block
import prog8.ast.statements.Subroutine
@@ -82,7 +83,7 @@ class Program(val name: String,
// Move a string literal into the internal, deduplicated, string pool
// replace it with a variable declaration that points to the entry in the pool.
if(string.parent is VarDecl) {
if(string.parent is VarDecl || string.parent is ContainmentCheck) {
// deduplication can only be performed safely for known-const strings (=string literals OUTSIDE OF A VARDECL)!
throw FatalAstException("cannot intern a string literal that's part of a vardecl")
}
@@ -280,12 +280,12 @@ private fun Prog8ANTLRParser.Functioncall_stmtContext.toAst(): Statement {
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
}
private fun Prog8ANTLRParser.FunctioncallContext.toAst(): FunctionCall {
private fun Prog8ANTLRParser.FunctioncallContext.toAst(): FunctionCallExpr {
val location = scoped_identifier().toAst()
return if(expression_list() == null)
FunctionCall(location, mutableListOf(), toPosition())
FunctionCallExpr(location, mutableListOf(), toPosition())
else
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition())
FunctionCallExpr(location, expression_list().toAst().toMutableList(), toPosition())
}
private fun Prog8ANTLRParser.InlineasmContext.toAst(): InlineAssembly {
@@ -516,28 +516,28 @@ private fun Prog8ANTLRParser.BooleanliteralContext.toAst() = when(text) {
private fun Prog8ANTLRParser.ArrayliteralContext.toAst() : Array<Expression> =
expression().map { it.toAst() }.toTypedArray()
private fun Prog8ANTLRParser.If_stmtContext.toAst(): IfStatement {
private fun Prog8ANTLRParser.If_stmtContext.toAst(): IfElse {
val condition = expression().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val elseStatements = else_part()?.toAst() ?: mutableListOf()
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
?: statement().toPosition())
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
return IfStatement(condition, trueScope, elseScope, toPosition())
return IfElse(condition, trueScope, elseScope, toPosition())
}
private fun Prog8ANTLRParser.Else_partContext.toAst(): MutableList<Statement> {
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
}
private fun Prog8ANTLRParser.Branch_stmtContext.toAst(): BranchStatement {
private fun Prog8ANTLRParser.Branch_stmtContext.toAst(): Branch {
val branchcondition = branchcondition().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val elseStatements = else_part()?.toAst() ?: mutableListOf()
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
?: statement().toPosition())
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
return BranchStatement(branchcondition, trueScope, elseScope, toPosition())
return Branch(branchcondition, trueScope, elseScope, toPosition())
}
private fun Prog8ANTLRParser.BranchconditionContext.toAst() = BranchCondition.valueOf(
@@ -581,10 +581,10 @@ private fun Prog8ANTLRParser.UntilloopContext.toAst(): UntilLoop {
return UntilLoop(scope, untilCondition, toPosition())
}
private fun Prog8ANTLRParser.WhenstmtContext.toAst(): WhenStatement {
private fun Prog8ANTLRParser.WhenstmtContext.toAst(): When {
val condition = expression().toAst()
val choices = this.when_choice()?.map { it.toAst() }?.toMutableList() ?: mutableListOf()
return WhenStatement(condition, choices, toPosition())
return When(condition, choices, toPosition())
}
private fun Prog8ANTLRParser.When_choiceContext.toAst(): WhenChoice {
@@ -7,14 +7,25 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import java.util.*
import kotlin.math.abs
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("&", "|", "^")
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("&", "|", "^")
fun invertedComparisonOperator(operator: String) =
when (operator) {
"==" -> "!="
"!=" -> "=="
"<" -> ">="
">" -> "<="
"<=" -> ">"
">=" -> "<"
else -> null
}
sealed class Expression: Node {
@@ -56,8 +67,8 @@ sealed class Expression: Node {
is RangeExpr -> {
(other is RangeExpr && other.from==from && other.to==to && other.step==step)
}
is FunctionCall -> {
(other is FunctionCall && other.target.nameInSource == target.nameInSource
is FunctionCallExpr -> {
(other is FunctionCallExpr && other.target.nameInSource == target.nameInSource
&& other.args.size == args.size
&& other.args.zip(args).all { it.first isSameAs it.second } )
}
@@ -209,6 +220,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
"<=", ">=",
"==", "!=" -> InferredTypes.knownFor(DataType.UBYTE)
"<<", ">>" -> leftDt
"in" -> InferredTypes.knownFor(DataType.UBYTE)
else -> throw FatalAstException("resulting datatype check for invalid operator $operator")
}
}
@@ -477,7 +489,12 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
}
override fun referencesIdentifier(nameInSource: List<String>) = false
override fun constValue(program: Program) = this
override fun constValue(program: Program): NumericLiteralValue {
return copy().also {
if(::parent.isInitialized)
it.parent = parent
}
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@@ -786,6 +803,42 @@ class RangeExpr(var from: Expression,
return "RangeExpr(from $from, to $to, step $step, pos=$position)"
}
fun toConstantIntegerRange(): IntProgression? {
fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
return when {
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
else -> fromVal..toVal step stepVal
}
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
}
}
}
val fromLv = from as? NumericLiteralValue
val toLv = to as? NumericLiteralValue
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 = stepLv.number.toInt()
return makeRange(fromVal, toVal, stepVal)
}
fun size(): Int? {
val fromLv = (from as? NumericLiteralValue)
val toLv = (to as? NumericLiteralValue)
if(fromLv==null || toLv==null)
return null
return toConstantIntegerRange()?.count()
}
}
@@ -796,7 +849,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
fun targetStatement(program: Program) =
if(nameInSource.size==1 && nameInSource[0] in program.builtinFunctions.names)
BuiltinFunctionStatementPlaceholder(nameInSource[0], position, parent)
BuiltinFunctionPlaceholder(nameInSource[0], position, parent)
else
definingScope.lookup(nameInSource)
@@ -853,9 +906,9 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
}
}
class FunctionCall(override var target: IdentifierReference,
override var args: MutableList<Expression>,
override val position: Position) : Expression(), IFunctionCall {
class FunctionCallExpr(override var target: IdentifierReference,
override var args: MutableList<Expression>,
override val position: Position) : Expression(), IFunctionCall {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@@ -864,7 +917,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 fun copy() = FunctionCallExpr(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) {
@@ -910,7 +963,7 @@ class FunctionCall(override var target: IdentifierReference,
return InferredTypes.knownFor(constVal.type)
val stmt = target.targetStatement(program) ?: return InferredTypes.unknown()
when (stmt) {
is BuiltinFunctionStatementPlaceholder -> {
is BuiltinFunctionPlaceholder -> {
if(target.nameInSource[0] == "set_carry" || target.nameInSource[0]=="set_irqd" ||
target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") {
return InferredTypes.void() // these have no return value
@@ -934,3 +987,97 @@ class FunctionCall(override var target: IdentifierReference,
}
}
}
class ContainmentCheck(var element: Expression,
var iterable: Expression,
override val position: Position): Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
element.parent = this
iterable.linkParents(this)
}
override val isSimple: Boolean = false
override fun copy() = ContainmentCheck(element.copy(), iterable.copy(), position)
override fun constValue(program: Program): NumericLiteralValue? {
val elementConst = element.constValue(program)
if(elementConst!=null) {
when(iterable){
is ArrayLiteralValue -> {
val exists = (iterable as ArrayLiteralValue).value.any { it.constValue(program)==elementConst }
return NumericLiteralValue.fromBoolean(exists, position)
}
is RangeExpr -> {
val intRange = (iterable as RangeExpr).toConstantIntegerRange()
if(intRange!=null && elementConst.type in IntegerDatatypes) {
val exists = elementConst.number.toInt() in intRange
return NumericLiteralValue.fromBoolean(exists, position)
}
}
is StringLiteralValue -> {
if(elementConst.type in ByteDatatypes) {
val stringval = iterable as StringLiteralValue
val exists = program.encoding.encodeString(stringval.value, stringval.altEncoding).contains(elementConst.number.toInt().toUByte() )
return NumericLiteralValue.fromBoolean(exists, position)
}
}
else -> {}
}
}
when(iterable){
is ArrayLiteralValue -> {
val array= iterable as ArrayLiteralValue
if(array.value.isEmpty())
return NumericLiteralValue.fromBoolean(false, position)
}
is RangeExpr -> {
val size = (iterable as RangeExpr).size()
if(size!=null && size==0)
return NumericLiteralValue.fromBoolean(false, position)
}
is StringLiteralValue -> {
if((iterable as StringLiteralValue).value.isEmpty())
return NumericLiteralValue.fromBoolean(false, position)
}
else -> {}
}
return null
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>): Boolean {
if(element is IdentifierReference)
return element.referencesIdentifier(nameInSource)
return iterable.referencesIdentifier(nameInSource)
}
override fun inferType(program: Program) = InferredTypes.knownFor(DataType.UBYTE)
override fun replaceChildNode(node: Node, replacement: Node) {
if(replacement !is Expression)
throw FatalAstException("invalid replace")
if(node===element)
element=replacement
else if(node===iterable)
iterable=replacement
else
throw FatalAstException("invalid replace")
}
}
fun invertCondition(cond: Expression): BinaryExpression? {
if(cond is BinaryExpression) {
val invertedOperator = invertedComparisonOperator(cond.operator)
if (invertedOperator != null)
return BinaryExpression(cond.left, invertedOperator, cond.right, cond.position)
}
return null
}
@@ -49,7 +49,7 @@ sealed class Statement : Node {
}
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position, override var parent: Node) : Statement() {
class BuiltinFunctionPlaceholder(val name: String, override val position: Position, override var parent: Node) : Statement() {
override fun linkParents(parent: Node) {}
override fun accept(visitor: IAstVisitor) = throw FatalAstException("should not iterate over this node")
override fun accept(visitor: AstWalker, parent: Node) = throw FatalAstException("should not iterate over this node")
@@ -130,7 +130,7 @@ data class Label(override val name: String, override val position: Position) : S
override fun toString()= "Label(name=$name, pos=$position)"
}
open class Return(var value: Expression?, final override val position: Position) : Statement() {
class Return(var value: Expression?, override val position: Position) : Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@@ -171,9 +171,9 @@ enum class ZeropageWish {
NOT_IN_ZEROPAGE
}
open class VarDecl(val type: VarDeclType,
class VarDecl(val type: VarDeclType,
private val declaredDatatype: DataType,
val zeropage: ZeropageWish,
var zeropage: ZeropageWish,
var arraysize: ArrayIndex?,
override val name: String,
var value: Expression?,
@@ -181,7 +181,7 @@ open class VarDecl(val type: VarDeclType,
val autogeneratedDontRemove: Boolean,
val sharedWithAsm: Boolean,
val subroutineParameter: SubroutineParameter?,
final override val position: Position) : Statement(), INamedStatement {
override val position: Position) : Statement(), INamedStatement {
override lateinit var parent: Node
var allowInitializeWithZero = true
@@ -299,7 +299,7 @@ class ArrayIndex(var indexExpr: Expression,
override fun copy() = ArrayIndex(indexExpr.copy(), position)
}
open class Assignment(var target: AssignTarget, var value: Expression, final override val position: Position) : Statement() {
class Assignment(var target: AssignTarget, var value: Expression, override val position: Position) : Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@@ -348,7 +348,7 @@ open class Assignment(var target: AssignTarget, var value: Expression, final ove
}
}
if(binExpr.operator in associativeOperators) {
if(binExpr.operator in AssociativeOperators) {
if (binExpr.left !is BinaryExpression && binExpr.right isSameAs target)
return true // A = v <associative-operator> A
@@ -499,12 +499,11 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val
override fun toString() = "PostIncrDecr(op: $operator, target: $target, pos=$position)"
}
open class Jump(val address: UInt?,
val identifier: IdentifierReference?,
val generatedLabel: String?, // can be used in code generation scenarios
override val position: Position) : Statement() {
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
@@ -521,11 +520,22 @@ open class Jump(val address: UInt?,
}
// a GoSub is ONLY created internally for calling subroutines
class GoSub(address: UInt?, identifier: IdentifierReference?, generatedLabel: String?, position: Position) :
Jump(address, identifier, generatedLabel, position) {
class GoSub(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
override val isGosub = true
override fun linkParents(parent: Node) {
this.parent = parent
identifier?.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun copy() = GoSub(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() =
"GoSub(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
}
@@ -595,7 +605,7 @@ class AnonymousScope(override var statements: MutableList<Statement>,
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class NopStatement(override val position: Position): Statement() {
class Nop(override val position: Position): Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@@ -603,7 +613,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 copy() = Nop(position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
@@ -744,10 +754,10 @@ open class SubroutineParameter(val name: String,
override fun toString() = "Param($type:$name)"
}
class IfStatement(var condition: Expression,
var truepart: AnonymousScope,
var elsepart: AnonymousScope,
override val position: Position) : Statement() {
class IfElse(var condition: Expression,
var truepart: AnonymousScope,
var elsepart: AnonymousScope,
override val position: Position) : Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@@ -774,10 +784,10 @@ class IfStatement(var condition: Expression,
}
class BranchStatement(var condition: BranchCondition,
var truepart: AnonymousScope,
var elsepart: AnonymousScope,
override val position: Position) : Statement() {
class Branch(var condition: BranchCondition,
var truepart: AnonymousScope,
var elsepart: AnonymousScope,
override val position: Position) : Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@@ -909,9 +919,9 @@ class UntilLoop(var body: AnonymousScope,
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class WhenStatement(var condition: Expression,
var choices: MutableList<WhenChoice>,
override val position: Position): Statement() {
class When(var condition: Expression,
var choices: MutableList<WhenChoice>,
override val position: Position): Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
+53 -36
View File
@@ -67,7 +67,7 @@ interface IAstModification {
class SwapOperands(private val expr: BinaryExpression): IAstModification {
override fun perform() {
require(expr.operator in associativeOperators)
require(expr.operator in AssociativeOperators)
val tmp = expr.left
expr.left = expr.right
expr.right = tmp
@@ -85,25 +85,27 @@ abstract class AstWalker {
open fun before(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = noModifications
open fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> = noModifications
open fun before(block: Block, parent: Node): Iterable<IAstModification> = noModifications
open fun before(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun before(branch: Branch, parent: Node): Iterable<IAstModification> = noModifications
open fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> = noModifications
open fun before(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> = noModifications
open fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> = noModifications
open fun before(directive: Directive, parent: Node): Iterable<IAstModification> = noModifications
open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = noModifications
open fun before(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> = noModifications
open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = noModifications
open fun before(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun before(ifElse: IfElse, parent: Node): Iterable<IAstModification> = noModifications
open fun before(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = noModifications
open fun before(jump: Jump, parent: Node): Iterable<IAstModification> = noModifications
open fun before(gosub: GoSub, parent: Node): Iterable<IAstModification> = noModifications
open fun before(label: Label, parent: Node): Iterable<IAstModification> = noModifications
open fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = noModifications
open fun before(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = noModifications
open fun before(module: Module, parent: Node): Iterable<IAstModification> = noModifications
open fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun before(nop: Nop, parent: Node): Iterable<IAstModification> = noModifications
open fun before(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = noModifications
open fun before(program: Program): Iterable<IAstModification> = noModifications
@@ -116,7 +118,7 @@ abstract class AstWalker {
open fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = noModifications
open fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = noModifications
open fun before(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun before(whenStmt: When, parent: Node): Iterable<IAstModification> = noModifications
open fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = noModifications
@@ -125,26 +127,28 @@ abstract class AstWalker {
open fun after(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = noModifications
open fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> = noModifications
open fun after(block: Block, parent: Node): Iterable<IAstModification> = noModifications
open fun after(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun after(branch: Branch, parent: Node): Iterable<IAstModification> = noModifications
open fun after(breakStmt: Break, parent: Node): Iterable<IAstModification> = noModifications
open fun after(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = noModifications
open fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> = noModifications
open fun after(builtinFunctionPlaceholder: BuiltinFunctionPlaceholder, parent: Node): Iterable<IAstModification> = noModifications
open fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> = noModifications
open fun after(directive: Directive, parent: Node): Iterable<IAstModification> = noModifications
open fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = noModifications
open fun after(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> = noModifications
open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = noModifications
open fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> = noModifications
open fun after(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = noModifications
open fun after(jump: Jump, parent: Node): Iterable<IAstModification> = noModifications
open fun after(gosub: GoSub, parent: Node): Iterable<IAstModification> = noModifications
open fun after(label: Label, parent: Node): Iterable<IAstModification> = noModifications
open fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = noModifications
open fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = noModifications
open fun after(module: Module, parent: Node): Iterable<IAstModification> = noModifications
open fun after(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun after(nop: Nop, parent: Node): Iterable<IAstModification> = noModifications
open fun after(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = noModifications
open fun after(program: Program): Iterable<IAstModification> = noModifications
@@ -157,7 +161,7 @@ abstract class AstWalker {
open fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = noModifications
open fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = noModifications
open fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> = noModifications
open fun after(whenStmt: When, parent: Node): Iterable<IAstModification> = noModifications
open fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = noModifications
protected val modifications = mutableListOf<Triple<IAstModification, Node, Node>>()
@@ -226,6 +230,13 @@ abstract class AstWalker {
track(after(directive, parent), directive, parent)
}
fun visit(containment: ContainmentCheck, parent: Node) {
track(before(containment, parent), containment, parent)
containment.element.accept(this, containment)
containment.iterable.accept(this, containment)
track(after(containment, parent), containment, parent)
}
fun visit(block: Block, parent: Node) {
track(before(block, parent), block, parent)
block.statements.forEach { it.accept(this, block) }
@@ -245,11 +256,11 @@ abstract class AstWalker {
track(after(subroutine, parent), subroutine, parent)
}
fun visit(functionCall: FunctionCall, parent: Node) {
track(before(functionCall, parent), functionCall, parent)
functionCall.target.accept(this, functionCall)
functionCall.args.forEach { it.accept(this, functionCall) }
track(after(functionCall, parent), functionCall, parent)
fun visit(functionCallExpr: FunctionCallExpr, parent: Node) {
track(before(functionCallExpr, parent), functionCallExpr, parent)
functionCallExpr.target.accept(this, functionCallExpr)
functionCallExpr.args.forEach { it.accept(this, functionCallExpr) }
track(after(functionCallExpr, parent), functionCallExpr, parent)
}
fun visit(functionCallStatement: FunctionCallStatement, parent: Node) {
@@ -270,19 +281,25 @@ abstract class AstWalker {
track(after(jump, parent), jump, parent)
}
fun visit(ifStatement: IfStatement, parent: Node) {
track(before(ifStatement, parent), ifStatement, parent)
ifStatement.condition.accept(this, ifStatement)
ifStatement.truepart.accept(this, ifStatement)
ifStatement.elsepart.accept(this, ifStatement)
track(after(ifStatement, parent), ifStatement, parent)
fun visit(gosub: GoSub, parent: Node) {
track(before(gosub, parent), gosub, parent)
gosub.identifier?.accept(this, gosub)
track(after(gosub, parent), gosub, parent)
}
fun visit(branchStatement: BranchStatement, parent: Node) {
track(before(branchStatement, parent), branchStatement, parent)
branchStatement.truepart.accept(this, branchStatement)
branchStatement.elsepart.accept(this, branchStatement)
track(after(branchStatement, parent), branchStatement, parent)
fun visit(ifElse: IfElse, parent: Node) {
track(before(ifElse, parent), ifElse, parent)
ifElse.condition.accept(this, ifElse)
ifElse.truepart.accept(this, ifElse)
ifElse.elsepart.accept(this, ifElse)
track(after(ifElse, parent), ifElse, parent)
}
fun visit(branch: Branch, parent: Node) {
track(before(branch, parent), branch, parent)
branch.truepart.accept(this, branch)
branch.elsepart.accept(this, branch)
track(after(branch, parent), branch, parent)
}
fun visit(range: RangeExpr, parent: Node) {
@@ -422,16 +439,16 @@ abstract class AstWalker {
track(after(inlineAssembly, parent), inlineAssembly, parent)
}
fun visit(nopStatement: NopStatement, parent: Node) {
track(before(nopStatement, parent), nopStatement, parent)
track(after(nopStatement, parent), nopStatement, parent)
fun visit(nop: Nop, parent: Node) {
track(before(nop, parent), nop, parent)
track(after(nop, parent), nop, parent)
}
fun visit(whenStatement: WhenStatement, parent: Node) {
track(before(whenStatement, parent), whenStatement, parent)
whenStatement.condition.accept(this, whenStatement)
whenStatement.choices.forEach { it.accept(this, whenStatement) }
track(after(whenStatement, parent), whenStatement, parent)
fun visit(whenStmt: When, parent: Node) {
track(before(whenStmt, parent), whenStmt, parent)
whenStmt.condition.accept(this, whenStmt)
whenStmt.choices.forEach { it.accept(this, whenStmt) }
track(after(whenStmt, parent), whenStmt, parent)
}
fun visit(whenChoice: WhenChoice, parent: Node) {
+23 -14
View File
@@ -26,6 +26,11 @@ interface IAstVisitor {
fun visit(directive: Directive) {
}
fun visit(containment: ContainmentCheck) {
containment.element.accept(this)
containment.iterable.accept(this)
}
fun visit(block: Block) {
block.statements.forEach { it.accept(this) }
}
@@ -39,9 +44,9 @@ interface IAstVisitor {
subroutine.statements.forEach { it.accept(this) }
}
fun visit(functionCall: FunctionCall) {
functionCall.target.accept(this)
functionCall.args.forEach { it.accept(this) }
fun visit(functionCallExpr: FunctionCallExpr) {
functionCallExpr.target.accept(this)
functionCallExpr.args.forEach { it.accept(this) }
}
fun visit(functionCallStatement: FunctionCallStatement) {
@@ -56,15 +61,19 @@ interface IAstVisitor {
jump.identifier?.accept(this)
}
fun visit(ifStatement: IfStatement) {
ifStatement.condition.accept(this)
ifStatement.truepart.accept(this)
ifStatement.elsepart.accept(this)
fun visit(gosub: GoSub) {
gosub.identifier?.accept(this)
}
fun visit(branchStatement: BranchStatement) {
branchStatement.truepart.accept(this)
branchStatement.elsepart.accept(this)
fun visit(ifElse: IfElse) {
ifElse.condition.accept(this)
ifElse.truepart.accept(this)
ifElse.elsepart.accept(this)
}
fun visit(branch: Branch) {
branch.truepart.accept(this)
branch.elsepart.accept(this)
}
fun visit(range: RangeExpr) {
@@ -160,12 +169,12 @@ interface IAstVisitor {
fun visit(inlineAssembly: InlineAssembly) {
}
fun visit(nopStatement: NopStatement) {
fun visit(nop: Nop) {
}
fun visit(whenStatement: WhenStatement) {
whenStatement.condition.accept(this)
whenStatement.choices.forEach { it.accept(this) }
fun visit(whenStmt: When) {
whenStmt.condition.accept(this)
whenStmt.choices.forEach { it.accept(this) }
}
fun visit(whenChoice: WhenChoice) {
@@ -4,9 +4,7 @@ import prog8.ast.base.FatalAstException
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.AssignTarget
import kotlin.math.abs
fun AssignTarget.isIOAddress(machine: IMachineDefinition): Boolean {
val memAddr = memoryAddress
@@ -49,39 +47,3 @@ fun AssignTarget.isIOAddress(machine: IMachineDefinition): Boolean {
else -> return false
}
}
fun RangeExpr.toConstantIntegerRange(): IntProgression? {
fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
return when {
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
else -> fromVal..toVal step stepVal
}
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
}
}
}
val fromLv = from as? NumericLiteralValue
val toLv = to as? NumericLiteralValue
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 = stepLv.number.toInt()
return makeRange(fromVal, toVal, stepVal)
}
fun RangeExpr.size(): Int? {
val fromLv = (from as? NumericLiteralValue)
val toLv = (to as? NumericLiteralValue)
if(fromLv==null || toLv==null)
return null
return toConstantIntegerRange()?.count()
}
@@ -6,7 +6,7 @@ import prog8.ast.Program
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.FunctionCallExpr
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
@@ -59,15 +59,15 @@ class CallGraph(private val program: Program) : IAstVisitor {
super.visit(directive)
}
override fun visit(functionCall: FunctionCall) {
val otherSub = functionCall.target.targetSubroutine(program)
override fun visit(functionCallExpr: FunctionCallExpr) {
val otherSub = functionCallExpr.target.targetSubroutine(program)
if (otherSub != null) {
functionCall.definingSubroutine?.let { thisSub ->
functionCallExpr.definingSubroutine?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub) + otherSub
calledBy[otherSub] = calledBy.getValue(otherSub) + functionCall
calledBy[otherSub] = calledBy.getValue(otherSub) + functionCallExpr
}
}
super.visit(functionCall)
super.visit(functionCallExpr)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
@@ -103,6 +103,17 @@ class CallGraph(private val program: Program) : IAstVisitor {
super.visit(jump)
}
override fun visit(gosub: GoSub) {
val otherSub = gosub.identifier?.targetSubroutine(program)
if (otherSub != null) {
gosub.definingSubroutine?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub) + otherSub
calledBy[otherSub] = calledBy.getValue(otherSub) + gosub
}
}
super.visit(gosub)
}
override fun visit(identifier: IdentifierReference) {
allIdentifiersAndTargets[Pair(identifier, identifier.position)] = identifier.targetStatement(program)!!
}
@@ -114,12 +125,12 @@ class CallGraph(private val program: Program) : IAstVisitor {
fun checkRecursiveCalls(errors: IErrorReporter) {
val cycles = recursionCycles()
if(cycles.any()) {
errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", Position.DUMMY)
errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", cycles[0][0].position)
val printed = mutableSetOf<Subroutine>()
for(chain in cycles) {
if(chain[0] !in printed) {
val chainStr = chain.joinToString(" <-- ") { "${it.name} at ${it.position}" }
errors.warn("Cycle in (a subroutine call in) $chainStr", Position.DUMMY)
errors.warn("Cycle in (a subroutine call in) $chainStr", chain[0].position)
printed.add(chain[0])
}
}
@@ -178,7 +189,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
}
fun usages(decl: VarDecl): List<IdentifierReference> {
if(decl.type!=VarDeclType.VAR || decl.autogeneratedDontRemove || decl.sharedWithAsm)
if(decl.type!=VarDeclType.VAR)
return emptyList()
if(decl.definingBlock !in usedBlocks)
@@ -28,5 +28,8 @@ class CompilationOptions(val output: OutputType,
// these are set based on command line arguments:
var slowCodegenWarnings: Boolean = false,
var optimize: Boolean = false,
var optimizeFloatExpressions: Boolean = false
var optimizeFloatExpressions: Boolean = false,
var dontReinitGlobals: Boolean = false,
var asmQuiet: Boolean = false,
var asmListfile: Boolean = false
)
@@ -11,5 +11,5 @@ const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2"
interface IAssemblyProgram {
val valid: Boolean
val name: String
fun assemble(quiet: Boolean, options: CompilationOptions): Int
fun assemble(options: CompilationOptions): Int
}
@@ -18,7 +18,6 @@ interface IMachineDefinition {
val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: Int
val POINTER_MEM_SIZE: Int
val ESTACK_LO: UInt
val ESTACK_HI: UInt
val BASIC_LOAD_ADDRESS : UInt
@@ -9,7 +9,7 @@ class ZeropageDepletedError(message: String) : Exception(message)
abstract class Zeropage(protected val options: CompilationOptions) {
abstract val SCRATCH_B1 : UInt // temp storage for a single byte
abstract val SCRATCH_REG : UInt // temp storage for a register
abstract val SCRATCH_REG : UInt // temp storage for a register, must be B1+1
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
@@ -28,21 +28,6 @@ abstract class Zeropage(protected val options: CompilationOptions) {
fun availableBytes() = if(options.zeropage== ZeropageType.DONTUSE) 0 else free.size
fun hasByteAvailable() = if(options.zeropage== ZeropageType.DONTUSE) false else free.isNotEmpty()
fun availableWords(): Int {
if(options.zeropage== ZeropageType.DONTUSE)
return 0
val words = free.windowed(2).filter { it[0] == it[1]-1u }
var nonOverlappingWordsCount = 0
var prevMsbLoc = UInt.MAX_VALUE
for(w in words) {
if(w[0]!=prevMsbLoc) {
nonOverlappingWordsCount++
prevMsbLoc = w[1]
}
}
return nonOverlappingWordsCount
}
fun hasWordAvailable(): Boolean {
if(options.zeropage== ZeropageType.DONTUSE)
return false
@@ -65,7 +50,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
if(position!=null)
errors.warn("allocated a large value (float) in zeropage", position)
else
errors.warn("$scopedname: allocated a large value (float) in zeropage", position ?: Position.DUMMY)
errors.warn("$scopedname: allocated a large value (float) in zeropage", Position.DUMMY)
options.compTarget.machine.FLOAT_MEM_SIZE
} else throw InternalCompilerException("floating point option not enabled")
}
+14
View File
@@ -12,3 +12,17 @@
max-width: 100%;
overflow: visible;
}
html {
font-size: 13pt;
}
p {
font-size: 13pt;
}
.rst-content .linenodiv pre,
.rst-content div[class^=highlight] pre,
.rst-content pre.literal-block {
font-size: 11pt;
}
+11 -1
View File
@@ -99,7 +99,7 @@ One or more .p8 module files
``-target <compilation target>``
Sets the target output of the compiler, currently 'c64' and 'cx16' are valid targets.
c64 = Commodore-64, cx16 = Commander X16.
c64 = Commodore 64, c128 = Commodore 128, cx16 = Commander X16.
Default = c64
``-srcdirs <pathlist>``
@@ -124,6 +124,13 @@ One or more .p8 module files
Don't perform any code optimizations.
Useful for debugging or faster compilation cycles.
``-noreinit``
Don't create code to reinitialize the global (block level) variables on every run of the program.
Also means that all such variables are no longer placed in the zero page.
Sometimes the program will be a lot shorter when using this, but sometimes the opposite happens.
When using this option, it is no longer be possible to run the program correctly more than once!
*Experimental feature*: still has some problems!
``-optfloatx``
Also optimize float expressions if optimizations are enabled.
Warning: can increase program size significantly if a lot of floating point expressions are used.
@@ -144,6 +151,9 @@ One or more .p8 module files
``-quietasm``
Don't print assembler output results.
``-asmlist``
Generate an assembler listing file as well.
Module source code files
------------------------
+14 -9
View File
@@ -18,6 +18,7 @@ This CPU is from the late 1970's and early 1980's and was used in many home comp
such as the `Commodore-64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
The language aims to provide many conveniences over raw assembly code (even when using a macro assembler),
while still being low level enough to create high performance programs.
You can compile programs for various machines with this CPU such as the Commodore-64 and Commodore-128, and the Commander X16.
Prog8 is copyright © Irmen de Jong (irmen@razorvine.net | http://www.razorvine.net).
@@ -54,6 +55,9 @@ Language features
- Subroutines with parameters and return values
- Complex nested expressions are possible
- Variables are allocated statically
- Conditional branches to map directly on processor branch instructions
- ``when`` statement to avoid if-else chains
- ``in`` expression for concise and efficient multi-value/containment test
- Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters
- Variable data types include signed and unsigned bytes and words, arrays, strings.
- Floating point math also supported if the target system provides floating point library routines (C64 and Cx16 both do).
@@ -61,8 +65,8 @@ Language features
- High-level code optimizations, such as const-folding, expression and statement simplifications/rewriting.
- Many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
- Programs can be run multiple times without reloading because of automatic variable (re)initializations.
- Supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, also on the C64.
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
- Supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, also on the other machines.
- If you only use standard kernal and core prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag)!
Code example
@@ -170,18 +174,18 @@ If you're targeting the CommanderX16 instead, there's a choice of the official `
and the unofficial `box16 <https://github.com/indigodarkwolf/box16>`_ (you can select which one you want to launch
using the ``-emu`` or ``-emu2`` command line options)
.. attention:: **Commander-X16 V38 versus V39**
.. attention:: **Commander-X16 r38 versus r39**
Starting with Prog8 7.0 the CX16 compilation target is configured for the upcoming v39 version of the emulator
Starting with Prog8 7.0 the CX16 compilation target is configured for the upcoming r39 version of the emulator
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
Unfortunately, r39 has not yet been officially released. At the time of writing, r38 is still
the latest official release. So, you have to either compile the r39 emulator+roms from the source in git yourself
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!
A cx16 program compiled by prog8 7.0 is meant for r39 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
the emulator v39 is officially released - but you won't be able to benefit from the compiler improvements
made since the previous release of prog8.
the emulator r39 is officially released - but you won't be able to benefit from the compiler improvements
made since that old release of prog8.
@@ -195,6 +199,7 @@ using the ``-emu`` or ``-emu2`` command line options)
libraries.rst
targetsystem.rst
technical.rst
portingguide.rst
todo.rst
+37 -31
View File
@@ -16,9 +16,9 @@ with Prog8 are written like this.
You can ``%import`` and use these modules explicitly, but the compiler may also import one or more
of these library modules automatically as required.
For full details on what is available in the libraries, look at their source code here:
https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib
.. note::
For full details on what is available in the libraries, please study their source code here:
https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib
.. caution::
The resulting compiled binary program *only works on the target machine it was compiled for*.
@@ -67,19 +67,6 @@ sys (part of syslib)
Efficiently set a part of memory to the given (u)word value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
``rsave()``
Saves the CPU registers and the status flags.
You can now more or less 'safely' use the registers directly, until you
restore them again so the generated code can carry on normally.
Note: it's not needed to rsave() before an asm subroutine that clobbers the X register
(which is used as the internal evaluation stack pointer).
The compiler will take care of this situation automatically.
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* saved.
``rrestore()``
Restores the CPU registers and the status flags from previously saved values.
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* restored.
``read_flags() -> ubyte``
Returns the current value of the CPU status register.
@@ -109,7 +96,7 @@ sys (part of syslib)
note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
note for cx16: the system irq handler has to be active for this to work (this is not required on c64)
``waitrastborder()`` (c64 target only)
``waitrastborder()`` (c64/c128 targets only)
busy wait till the raster position has reached the bottom screen border (approximately)
can be used to avoid screen flicker/tearing when updating screen contents.
note: a more accurate way to do this is by using a raster irq handler instead.
@@ -149,6 +136,15 @@ Provides several routines that deal with disk drive I/O, such as:
- load and save data from and to the disk
- delete and rename files on the disk
.. attention::
**CX16 specific note**
The cx16 emulators provide a convenience feature called 'host filesystem passthrough' where basically
the directory on your host system is treated as the "disk drive" in the emulator.
In this mode, most of the I/O routines in the machine's kernal, and by extension the ones in this
module, *don't work*. You have to use a sd-card mounted disk image to be able to use all routines.
Regular ``load()`` (but not ``load_raw()``) works just fine even with the host filesystem.
Another thing to pay attention to is the capitalization of filenames due to the petscii translation involved.
string
------
@@ -219,6 +215,24 @@ That also means though that it is constrained to 320*200 resolution on the Cx16
Use the ``gfx2`` library if you want full-screen graphics or non-monochrome drawing.
math
----
Low level math routines. You should not normally have to bother with this directly.
The compiler needs it to implement most of the math operations in your programs.
cx16logo
--------
A 'fun' module that contains the Commander X16 logo and that allows you
to print it anywhere on the screen.
prog8_lib
---------
Low level language support. You should not normally have to bother with this directly.
The compiler needs it for verious built-in system routines.
gfx2 (cx16 only)
-----------------
Full-screen multicolor bitmap graphics routines, available on the Cx16 machine only.
@@ -239,19 +253,11 @@ because the Commander X16's default colors for this (the first 16 colors) are to
and are quite different than how they looked on a VIC-II chip in a C-64.
math
----
Low level math routines. You should not normally have to bother with this directly.
The compiler needs it to implement most of the math operations in your programs.
cx16diskio (cx16 only)
-----------------------
Available for the Cx16 target. Contains extensions to the load and load_raw routines from the regular
diskio module, to deal with loading of potentially large files in to banked ram (HiRam).
Also contains a helper function to calculate the file size of a loaded file (although that is truncated
to 16 bits, 64Kb)
cx16logo
--------
A 'fun' module that contains the Commander X16 logo and that allows you
to print it anywhere on the screen.
prog8_lib
---------
Low level language support. You should not normally have to bother with this directly.
The compiler needs it for verious built-in system routines.
+78
View File
@@ -0,0 +1,78 @@
=============
Porting Guide
=============
Here is a guide for porting Prog8 to other compilation targets.
Answers to the questions below are used to configure the new target and supporting libraries.
CPU
---
#. 6502 or 65C02? (or strictly compatible with one of these)
#. can the **64tass** cross assembler create programs for the system? (if not, bad luck atm)
Memory Map
----------
Zero page
=========
#. *Absolute requirement:* Provide three times 2 consecutive bytes (i.e. three 16-bit pointers) in the Zero page that are free to use at all times.
#. Provide list of any additional free Zero page locations for a normal running system (basic + kernal enabled)
#. Provide list of any additional free Zero page locations when basic is off, but floating point routines should still work
#. Provide list of any additional free Zero page locations when only the kernal remains enabled
Only the three 16-bit pointers are absolutely required to be able to use prog8 on the system.
But more known available Zero page locations mean smaller and faster programs.
RAM, ROM, I/O
=============
#. what part(s) of the address space is RAM? What parts of the RAM can be used by user programs?
#. what is the load address of Basic programs?
#. what is a good load address of machine code programs?
#. what is the best place to put 2 pages (512 bytes total) of scratch area data in RAM?
#. what part(s) of the address space is ROM?
#. what part(s) of the address space is memory mapped I/O registers?
#. is there a banking system? How does it work (how do you select Ram/Rom banks)? How is the default bank configuration set?
Screen and Character encodings
------------------------------
#. provide the primary character encoding table that the system uses (i.e. how is text represented in memory)
#. provide alternate character encoding table (if any)
#. what are the system's character screen dimensions?
#. is there a screen matrix directly accessible in Ram? Provide addresses of the character matrix and color attributes matrix, if any.
ROM routines
------------
#. provide a list of the core ROM routines on the system, with names, addresses, and call signatures.
Ideally there are at least some routines to manipulate the screen and get some user input(clear, print text, print numbers, input strings from the keyboard)
Routines to initialize the system to a sane state and to do a warm reset are useful too.
The more the merrier.
Floating point
==============
Prog8 supports floating point math *if* the target system has floating point math routines in ROM.
If the machine has this:
#. what is the binary representation format of the floating point numbers? (how many bytes, how the bits are set up)
#. what are the valid minimum negative and maximum positive floating point values?
#. provide a list of the floating point math routines in ROM: name, address, call signature.
Support libraries
-----------------
The most important libraries are ``syslib`` and ``textio``.
``syslib`` *has* to provide several system level functions such as how to initialize the machine to a sane state,
and how to warm reset it, etc.
``textio`` contains the text output and input routines, it's very welcome if they are implemented also for
the new target system.
There are several other support libraries that you may want to port (``diskio``, ``graphics`` to name a few).
Also ofcourse if there are unique things available on the new target system, don't hesitate to provide
extensions to the ``syslib`` or perhaps a new special custom library altogether.
Loaded 100 of 115 files, more files were not shown because too many files have changed in this diff. Show more