Compare commits

...

70 Commits
v1.20 ... v1.51

Author SHA1 Message Date
065587525e version 2019-08-11 17:43:14 +02:00
58e5d5c071 hash 2019-08-11 17:32:28 +02:00
b44e76db57 fix any/all assembly routine, added asm for min/max/sum/ etc aggregates
removed avg function because of hidden internal overflow issues
2019-08-11 16:13:09 +02:00
2ce6bc5946 fix strlen 2019-08-11 14:02:53 +02:00
fe5b225732 asmsub stack arg 2019-08-11 12:29:18 +02:00
d499e40a4b doc tweaks 2019-08-11 10:56:36 +02:00
62a66d89c6 was not needed 2019-08-11 10:15:34 +02:00
e1b26ae287 readme 2019-08-10 21:38:08 +02:00
1c151f4a3f remove dysfunctional repl 2019-08-10 21:36:26 +02:00
8917926996 new version 2019-08-10 20:45:41 +02:00
b54a9b9831 fix output of word arrays containing addressofs 2019-08-10 20:43:27 +02:00
f08906dba1 fix byte->word typecast 2019-08-10 14:20:42 +02:00
a6bba824d3 fixed some array codegen issues 2019-08-10 12:55:27 +02:00
fd84152a2b import cleanups 2019-08-09 02:21:04 +02:00
3466106119 fixed some array codegen issues 2019-08-09 02:15:31 +02:00
c79b587eea nonconst forloops (bytes) 2019-08-08 23:13:02 +02:00
4862fb7db1 asmsub return value in registers is now put on evalstack, and loopvar sequence numbering 2019-08-08 00:13:58 +02:00
2136db0e61 fix auto var naming collisions 2019-08-07 22:25:57 +02:00
2f0c0f6fcd fix function arguments 2019-08-07 02:31:27 +02:00
7ddc01f883 added continuous compilation mode (file watching) 2019-08-05 23:36:24 +02:00
126c2162f1 syntax fix in readme 2019-08-05 21:11:58 +02:00
094c8ab94c Merge branch 'asmgen2-ast-only' 2019-08-05 21:07:32 +02:00
efe2723874 version 2019-08-05 21:06:41 +02:00
bccfeb2fa2 fix some unittests 2019-08-05 21:04:15 +02:00
d498d5445c added more examples/test programs 2019-08-05 21:01:41 +02:00
5095d090cc added optimized multiplications to asmgen2 2019-08-05 21:00:55 +02:00
6544fcdc36 fixed output of force_output blocks 2019-08-04 23:08:58 +02:00
e834924857 more ++ and -- code, 'dontuse' zeropage option 2019-08-04 22:44:20 +02:00
2c3b8a9819 more ++ and -- code, 'dontuse' zeropage option 2019-08-04 22:35:27 +02:00
309c82fc9e fixed some compiler errors 2019-08-04 19:54:32 +02:00
0f91ce6441 removed a few more hazardous zp addresses 2019-08-04 19:40:31 +02:00
f29ec3b4e1 relaxed symbol shadowing 2019-08-04 18:52:03 +02:00
cc1fc869cf fix param type casts for builtin functions 2019-08-04 18:25:00 +02:00
0431d3cddc implemented asm for continue and break 2019-08-04 16:05:50 +02:00
a1cd202cd2 some more array asm 2019-08-04 15:33:00 +02:00
b842493cf0 trying to fix arithmetic and funcion calls and var scoping issues 2019-08-03 13:21:38 +02:00
4718f09cb7 trying to fix arithmetic and funcion calls 2019-08-03 01:51:12 +02:00
e9c357a885 fix range typing issues and function call param cleanup bug for asmsub 2019-08-02 01:26:28 +02:00
fb00ff74d1 simplistic repeat and while loops 2019-08-01 21:23:55 +02:00
b740b079db simplified mapping of builtin functions to just a jsr 2019-08-01 21:03:21 +02:00
6394841041 fix byte/word add/sub mixup 2019-08-01 20:42:09 +02:00
3f4050c647 more for loops, words 2019-08-01 00:35:25 +02:00
82f01d84c2 more for loops 2019-07-31 22:15:20 +02:00
299ea72d70 various for loops 2019-07-31 21:47:30 +02:00
50aa286d3a begin of for asm 2019-07-31 00:54:04 +02:00
6f7322150f fix string literal replacing by identifierref 2019-07-31 00:14:12 +02:00
cc9965cc96 improved deduction of array datatypes 2019-07-30 23:35:25 +02:00
ae90a957c6 fix var prefix issues in asm gen of anonscopes 2019-07-30 21:13:52 +02:00
8cec032e7d more asm for byte writes to memory 2019-07-30 02:49:13 +02:00
3732ab1e62 fix compilation errors 2019-07-30 02:26:30 +02:00
fba149ee28 removed the ~ before block names 2019-07-29 23:11:13 +02:00
4661cba974 asm for when statements added 2019-07-29 22:47:04 +02:00
025be8cb7c fix infinte loop in constantfolding of when choices 2019-07-29 22:06:59 +02:00
3aea32551b fixes 2019-07-29 02:47:01 +02:00
8e8c112ff0 improved subroutine param ast checks, added asm for Carry parameter 2019-07-29 00:33:19 +02:00
b0dda08e74 assembler reserved symbols checked 2019-07-28 23:37:33 +02:00
2c25df122a merge strings in asm output 2019-07-28 21:29:49 +02:00
7cb5702b37 array asm 2019-07-28 21:03:09 +02:00
b7502c7eaa fixed some node update issues in Modifying Ast visitor 2019-07-28 15:18:53 +02:00
fed020825a some more asmgen v2; fixed duplicate label namings, if stmt, and vars in anon scopes 2019-07-28 13:12:13 +02:00
1c411897df some more asmgen v2, and seemingly useless assignments to memory variables are no longer optimized away 2019-07-27 03:11:15 +02:00
f94e241fb2 fix array datatypes in vardecls 2019-07-26 23:51:53 +02:00
757cbfd1ba readme 2019-07-24 00:45:04 +02:00
3de80319db readme 2019-07-24 00:43:37 +02:00
f9617d777a floats from rom 2019-07-24 00:39:01 +02:00
9961a404ae got rid of bytecode based compiler and vm 2019-07-23 20:44:11 +02:00
776c844d02 more ast-codegen v2 2019-07-23 01:36:49 +02:00
03782a37a2 begin of ast-codegen v2 2019-07-21 23:50:13 +02:00
173663380b slight optimization for creating the asmpatterns list 2019-07-20 22:37:16 +02:00
c6fdd65c63 shuffling some things around 2019-07-18 22:23:31 +02:00
108 changed files with 17039 additions and 6665 deletions

2
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,2 @@
# Default ignored files
/shelf/

1
.idea/modules.xml generated
View File

@ -2,6 +2,7 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/DeprecatedStackVm/DeprecatedStackVm.iml" filepath="$PROJECT_DIR$/DeprecatedStackVm/DeprecatedStackVm.iml" />
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
</component>
</module>

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
package prog8.compiler.intermediate
package compiler.intermediate
import prog8.vm.RuntimeValue
import prog8.vm.stackvm.Syscall
@ -15,8 +15,8 @@ open class Instruction(val opcode: Opcode,
val argStr = arg?.toString() ?: ""
val result =
when {
opcode==Opcode.LINE -> "_line $callLabel"
opcode==Opcode.INLINE_ASSEMBLY -> {
opcode== Opcode.LINE -> "_line $callLabel"
opcode== Opcode.INLINE_ASSEMBLY -> {
// inline assembly is not written out (it can't be processed as intermediate language)
// instead, it is converted into a system call that can be intercepted by the vm
if(callLabel!=null)
@ -24,10 +24,10 @@ open class Instruction(val opcode: Opcode,
else
"inline_assembly"
}
opcode==Opcode.INCLUDE_FILE -> {
opcode== Opcode.INCLUDE_FILE -> {
"include_file \"$callLabel\" $arg $arg2"
}
opcode==Opcode.SYSCALL -> {
opcode== Opcode.SYSCALL -> {
val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() }
"syscall $syscall"
}

View File

@ -1,4 +1,4 @@
package prog8.compiler.intermediate
package compiler.intermediate
import prog8.ast.antlr.escape
import prog8.ast.base.*
@ -34,7 +34,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
val memory = mutableMapOf<Int, List<RuntimeValue>>()
private lateinit var currentBlock: ProgramBlock
fun allocateZeropage(zeropage: Zeropage) {
fun allocateZeropage(zeropage: Zeropage) { // TODO not used anymore???
// allocates all @zp marked variables on the zeropage (for all blocks, as long as there is space in the ZP)
var notAllocated = 0
for(block in blocks) {
@ -85,7 +85,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
val branchOpcodes = setOf(Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW)
for(blk in blocks) {
val instructionsToReplace = mutableMapOf<Int, Instruction>()
blk.instructions.asSequence().withIndex().filter {it.value.opcode!=Opcode.LINE}.windowed(2).toList().forEach {
blk.instructions.asSequence().withIndex().filter {it.value.opcode!= Opcode.LINE }.windowed(2).toList().forEach {
if (it[1].value.opcode in branchOpcodes) {
if (it[0].value.opcode in pushvalue) {
val value = it[0].value.arg!!.asBoolean
@ -138,8 +138,8 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
for(blk in blocks) {
val instructionsToReplace = mutableMapOf<Int, Instruction>()
blk.instructions.asSequence().withIndex().filter {it.value.opcode!=Opcode.LINE}.windowed(2).toList().forEach {
if(it[0].value.opcode==Opcode.CALL && it[1].value.opcode==Opcode.RETURN) {
blk.instructions.asSequence().withIndex().filter {it.value.opcode!= Opcode.LINE }.windowed(2).toList().forEach {
if(it[0].value.opcode== Opcode.CALL && it[1].value.opcode== Opcode.RETURN) {
instructionsToReplace[it[1].index] = Instruction(Opcode.JUMP, callLabel = it[0].value.callLabel)
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
}
@ -315,12 +315,12 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_B_TO_F, Opcode.CAST_UB_TO_F-> {
Opcode.CAST_B_TO_F, Opcode.CAST_UB_TO_F -> {
val ins = Instruction(Opcode.PUSH_FLOAT, RuntimeValue(DataType.FLOAT, ins0.arg!!.integerValue().toDouble()))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_W_TO_F, Opcode.CAST_UW_TO_F-> throw CompilerException("invalid conversion following a byte")
Opcode.CAST_W_TO_F, Opcode.CAST_UW_TO_F -> throw CompilerException("invalid conversion following a byte")
Opcode.DISCARD_BYTE -> {
instructionsToReplace[index0] = Instruction(Opcode.NOP)
instructionsToReplace[index1] = Instruction(Opcode.NOP)
@ -462,7 +462,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
}
fun newBlock(name: String, address: Int?, options: Set<String>) {
currentBlock = ProgramBlock(name, address, force_output="force_output" in options)
currentBlock = ProgramBlock(name, address, force_output = "force_output" in options)
blocks.add(currentBlock)
}

View File

@ -1,4 +1,4 @@
package prog8.compiler.intermediate
package compiler.intermediate
enum class Opcode {

View File

@ -1,4 +1,4 @@
package prog8.compiler.target.c64
package compiler.target.c64.codegen
// note: to put stuff on the stack, we use Absolute,X addressing mode which is 3 bytes / 4 cycles
// possible space optimization is to use zeropage (indirect),Y which is 2 bytes, but 5 cycles
@ -12,6 +12,9 @@ import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.IntermediateProgram
import prog8.compiler.intermediate.LabelInstr
import prog8.compiler.intermediate.Opcode
import prog8.compiler.target.c64.AssemblyProgram
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.vm.RuntimeValue
import java.io.File
import java.util.*
@ -302,7 +305,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
val (structMembers, normalVars) = block.variables.partition { it.params.memberOfStruct!=null }
structMembers.forEach { vardecl2asm(it.scopedname, it.value, it.params) }
// leave outsort the other variables by type
// sort the other variables by type
out("; other variables sorted by type")
val sortedVars = normalVars.sortedBy { it.value.type }
for (variable in sortedVars) {
@ -533,7 +536,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
}
// add any matching patterns from the big list
for(pattern in patterns) {
for(pattern in Patterns.patterns) {
if(pattern.sequence.size > segment.size || (pattern.altSequence!=null && pattern.altSequence.size > segment.size))
continue // don't accept patterns that don't fit
val opcodesList = opcodes.subList(0, pattern.sequence.size)
@ -560,31 +563,31 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
Opcode.SHL_BYTE -> AsmFragment(" asl $variable+$index", 8)
Opcode.SHR_UBYTE -> AsmFragment(" lsr $variable+$index", 8)
Opcode.SHR_SBYTE -> AsmFragment(" lda $variable+$index | asl a | ror $variable+$index")
Opcode.SHL_WORD -> AsmFragment(" asl $variable+${index*2+1} | rol $variable+${index*2}", 8)
Opcode.SHR_UWORD -> AsmFragment(" lsr $variable+${index*2+1} | ror $variable+${index*2}", 8)
Opcode.SHR_SWORD -> AsmFragment(" lda $variable+${index*2+1} | asl a | ror $variable+${index*2+1} | ror $variable+${index*2}", 8)
Opcode.SHL_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
Opcode.SHR_UWORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.SHR_SWORD -> AsmFragment(" lda $variable+${index * 2 + 1} | asl a | ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.ROL_BYTE -> AsmFragment(" rol $variable+$index", 8)
Opcode.ROR_BYTE -> AsmFragment(" ror $variable+$index", 8)
Opcode.ROL_WORD -> AsmFragment(" rol $variable+${index*2+1} | rol $variable+${index*2}", 8)
Opcode.ROR_WORD -> AsmFragment(" ror $variable+${index*2+1} | ror $variable+${index*2}", 8)
Opcode.ROL_WORD -> AsmFragment(" rol $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
Opcode.ROR_WORD -> AsmFragment(" ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.ROL2_BYTE -> AsmFragment(" lda $variable+$index | cmp #\$80 | rol $variable+$index", 8)
Opcode.ROR2_BYTE -> AsmFragment(" lda $variable+$index | lsr a | bcc + | ora #\$80 |+ | sta $variable+$index", 10)
Opcode.ROL2_WORD -> AsmFragment(" asl $variable+${index*2+1} | rol $variable+${index*2} | bcc + | inc $variable+${index*2+1} |+",20)
Opcode.ROR2_WORD -> AsmFragment(" lsr $variable+${index*2+1} | ror $variable+${index*2} | bcc + | lda $variable+${index*2+1} | ora #\$80 | sta $variable+${index*2+1} |+", 30)
Opcode.ROL2_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2} | bcc + | inc $variable+${index * 2 + 1} |+", 20)
Opcode.ROR2_WORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2} | bcc + | lda $variable+${index * 2 + 1} | ora #\$80 | sta $variable+${index * 2 + 1} |+", 30)
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> AsmFragment(" inc $variable+$index", 2)
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> AsmFragment(" dec $variable+$index", 5)
Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_UW -> AsmFragment(" inc $variable+${index*2} | bne + | inc $variable+${index*2+1} |+")
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_UW -> AsmFragment(" lda $variable+${index*2} | bne + | dec $variable+${index*2+1} |+ | dec $variable+${index*2}")
Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_UW -> AsmFragment(" inc $variable+${index * 2} | bne + | inc $variable+${index * 2 + 1} |+")
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_UW -> AsmFragment(" lda $variable+${index * 2} | bne + | dec $variable+${index * 2 + 1} |+ | dec $variable+${index * 2}")
Opcode.INC_INDEXED_VAR_FLOAT -> AsmFragment(
"""
lda #<($variable+${index* MachineDefinition.Mflpt5.MemorySize})
ldy #>($variable+${index* MachineDefinition.Mflpt5.MemorySize})
"""
lda #<($variable+${index * MachineDefinition.Mflpt5.MemorySize})
ldy #>($variable+${index * MachineDefinition.Mflpt5.MemorySize})
jsr c64flt.inc_var_f
""")
Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment(
"""
lda #<($variable+${index* MachineDefinition.Mflpt5.MemorySize})
ldy #>($variable+${index* MachineDefinition.Mflpt5.MemorySize})
"""
lda #<($variable+${index * MachineDefinition.Mflpt5.MemorySize})
ldy #>($variable+${index * MachineDefinition.Mflpt5.MemorySize})
jsr c64flt.dec_var_f
""")

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
package prog8.compiler.target.c64
package compiler.target.c64.codegen
import prog8.compiler.CompilerException
import prog8.compiler.intermediate.Instruction
@ -459,8 +459,8 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.CAST_B_TO_F -> " jsr c64flt.stack_b2float"
Opcode.CAST_UW_TO_F -> " jsr c64flt.stack_uw2float"
Opcode.CAST_W_TO_F -> " jsr c64flt.stack_w2float"
Opcode.CAST_F_TO_UB -> " jsr c64flt.stack_float2uw"
Opcode.CAST_F_TO_B -> " jsr c64flt.stack_float2w"
Opcode.CAST_F_TO_UB -> " jsr c64flt.stack_float2ub"
Opcode.CAST_F_TO_B -> " jsr c64flt.stack_float2b"
Opcode.CAST_F_TO_UW -> " jsr c64flt.stack_float2uw"
Opcode.CAST_F_TO_W -> " jsr c64flt.stack_float2w"
Opcode.CAST_UB_TO_UW, Opcode.CAST_UB_TO_W -> " lda #0 | sta $ESTACK_HI_PLUS1_HEX,x" // clear the msb
@ -513,27 +513,27 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.BITOR_WORD -> " jsr prog8_lib.bitor_w"
Opcode.BITXOR_WORD -> " jsr prog8_lib.bitxor_w"
Opcode.REMAINDER_UB -> " jsr prog8_lib.remainder_ub"
Opcode.REMAINDER_UW -> " jsr prog8_lib.remainder_uw"
Opcode.REMAINDER_UB -> " jsr prog8_lib.remainder_ub"
Opcode.REMAINDER_UW -> " jsr prog8_lib.remainder_uw"
Opcode.GREATER_B -> " jsr prog8_lib.greater_b"
Opcode.GREATER_UB -> " jsr prog8_lib.greater_ub"
Opcode.GREATER_W -> " jsr prog8_lib.greater_w"
Opcode.GREATER_UW -> " jsr prog8_lib.greater_uw"
Opcode.GREATER_F -> " jsr c64flt.greater_f"
Opcode.GREATER_B -> " jsr prog8_lib.greater_b"
Opcode.GREATER_UB -> " jsr prog8_lib.greater_ub"
Opcode.GREATER_W -> " jsr prog8_lib.greater_w"
Opcode.GREATER_UW -> " jsr prog8_lib.greater_uw"
Opcode.GREATER_F -> " jsr c64flt.greater_f"
Opcode.GREATEREQ_B -> " jsr prog8_lib.greatereq_b"
Opcode.GREATEREQ_UB -> " jsr prog8_lib.greatereq_ub"
Opcode.GREATEREQ_W -> " jsr prog8_lib.greatereq_w"
Opcode.GREATEREQ_UW -> " jsr prog8_lib.greatereq_uw"
Opcode.GREATEREQ_F -> " jsr c64flt.greatereq_f"
Opcode.GREATEREQ_B -> " jsr prog8_lib.greatereq_b"
Opcode.GREATEREQ_UB -> " jsr prog8_lib.greatereq_ub"
Opcode.GREATEREQ_W -> " jsr prog8_lib.greatereq_w"
Opcode.GREATEREQ_UW -> " jsr prog8_lib.greatereq_uw"
Opcode.GREATEREQ_F -> " jsr c64flt.greatereq_f"
Opcode.EQUAL_BYTE -> " jsr prog8_lib.equal_b"
Opcode.EQUAL_WORD -> " jsr prog8_lib.equal_w"
Opcode.EQUAL_F -> " jsr c64flt.equal_f"
Opcode.EQUAL_BYTE -> " jsr prog8_lib.equal_b"
Opcode.EQUAL_WORD -> " jsr prog8_lib.equal_w"
Opcode.EQUAL_F -> " jsr c64flt.equal_f"
Opcode.NOTEQUAL_BYTE -> " jsr prog8_lib.notequal_b"
Opcode.NOTEQUAL_WORD -> " jsr prog8_lib.notequal_w"
Opcode.NOTEQUAL_F -> " jsr c64flt.notequal_f"
Opcode.NOTEQUAL_F -> " jsr c64flt.notequal_f"
Opcode.LESS_UB -> " jsr prog8_lib.less_ub"
Opcode.LESS_B -> " jsr prog8_lib.less_b"

View File

@ -17,7 +17,7 @@ which aims to provide many conveniences over raw assembly code (even when using
- easier program understanding (because it's higher level, and way more compact)
- modularity, symbol scoping, subroutines
- subroutines have enforced input- and output parameter definitions
- various data types other than just bytes (16-bit words, floats, strings, 16-bit register pairs)
- various data types other than just bytes (16-bit words, floats, strings)
- automatic variable allocations, automatic string variables and string sharing
- constant folding in expressions (compile-time evaluation)
- conditional branches
@ -26,12 +26,14 @@ which aims to provide many conveniences over raw assembly code (even when using
- automatic type conversions
- floating point operations (uses the C64 Basic ROM routines for this)
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- various code optimizations (code structure, logical and numerical expressions, unused code removal...)
- various code optimizations (code structure, logical and numerical expressions, unused code removal...)
- inline assembly allows you to have full control when every cycle or byte matters
Rapid edit-compile-run-debug cycle:
- use modern PC to work on
- quick compilation times (less than 1 second)
- quick compilation times (couple of seconds, and less than a second when using the continuous compilation mode)
- option to automatically run the program in the Vice emulator
- 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
@ -42,11 +44,13 @@ Rapid edit-compile-run-debug cycle:
It is mainly targeted at the Commodore-64 machine at this time.
Contributions to add support for other 8-bit (or other?!) machines are welcome.
Documentation is online at https://prog8.readthedocs.io/
Documentation/manual
--------------------
See https://prog8.readthedocs.io/
Required tools:
---------------
Required tools
--------------
[64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path.
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
@ -68,7 +72,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
%import c64utils
%zeropage basicsafe
~ main {
main {
ubyte[256] sieve
ubyte candidate_prime = 2

View File

@ -7,7 +7,7 @@
%option enable_floats
~ c64flt {
c64flt {
; ---- this block contains C-64 floating point related functions ----
const float PI = 3.141592653589793
@ -52,7 +52,7 @@ asmsub MOVMF (uword mflpt @ XY) clobbers(A,Y) = $bbd4 ; store fac1 to memory
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
asmsub FTOSWORDYA () clobbers(X) -> ubyte @ Y, ubyte @ A = $b1aa
asmsub FTOSWORDYA () clobbers(X) -> ubyte @ Y, ubyte @ A = $b1aa ; note: calls AYINT.
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
@ -196,8 +196,8 @@ sub print_f (float value) {
; ---- prints the floating point value (without a newline) using basic rom routines.
%asm {{
stx c64.SCRATCH_ZPREGX
lda #<print_f_value
ldy #>print_f_value
lda #<value
ldy #>value
jsr MOVFM ; load float into fac1
jsr FOUT ; fac1 to string in A/Y
jsr c64.STROUT ; print string in A/Y
@ -310,7 +310,7 @@ stack_uw2float .proc
jmp push_fac1_as_result
.pend
stack_float2w .proc
stack_float2w .proc ; also used for float2b
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr AYINT
@ -323,7 +323,7 @@ stack_float2w .proc
rts
.pend
stack_float2uw .proc
stack_float2uw .proc ; also used for float2ub
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr GETADR

View File

@ -6,7 +6,7 @@
; indent format: TABS, size=8
~ c64 {
c64 {
const uword ESTACK_LO = $ce00 ; evaluation stack (lsb)
const uword ESTACK_HI = $cf00 ; evaluation stack (msb)
&ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
@ -21,7 +21,7 @@
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
&ubyte COLOR = $0286 ; cursor color
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
&uword CINV = $0314 ; IRQ vector
@ -202,12 +202,12 @@ asmsub CINT () clobbers(A,X,Y) = $FF81 ; (alias: SCINIT) initialize scree
asmsub IOINIT () clobbers(A, X) = $FF84 ; initialize I/O devices (CIA, SID, IRQ)
asmsub RAMTAS () clobbers(A,X,Y) = $FF87 ; initialize RAM, tape buffer, screen
asmsub RESTOR () clobbers(A,X,Y) = $FF8A ; restore default I/O vectors
asmsub VECTOR (ubyte dir @ Pc, uword userptr @ XY) clobbers(A,Y) = $FF8D ; read/set I/O vector table
asmsub VECTOR (uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) = $FF8D ; read/set I/O vector table
asmsub SETMSG (ubyte value @ A) = $FF90 ; set Kernal message control flag
asmsub SECOND (ubyte address @ A) clobbers(A) = $FF93 ; (alias: LSTNSA) send secondary address after LISTEN
asmsub TKSA (ubyte address @ A) clobbers(A) = $FF96 ; (alias: TALKSA) send secondary address after TALK
asmsub MEMTOP (ubyte dir @ Pc, uword address @ XY) -> uword @ XY = $FF99 ; read/set top of memory pointer
asmsub MEMBOT (ubyte dir @ Pc, uword address @ XY) -> uword @ XY = $FF9C ; read/set bottom of memory pointer
asmsub MEMTOP (uword address @ XY, ubyte dir @ Pc) -> uword @ XY = $FF99 ; read/set top of memory pointer
asmsub MEMBOT (uword address @ XY, ubyte dir @ Pc) -> uword @ XY = $FF9C ; read/set bottom of memory pointer
asmsub SCNKEY () clobbers(A,X,Y) = $FF9F ; scan the keyboard
asmsub SETTMO (ubyte timeout @ A) = $FFA2 ; set time-out flag for IEEE bus
asmsub ACPTR () -> ubyte @ A = $FFA5 ; (alias: IECIN) input byte from serial bus
@ -235,7 +235,7 @@ asmsub GETIN () clobbers(X,Y) -> ubyte @ A = $FFE4 ; (via 810 ($32A)) get a
asmsub CLALL () clobbers(A,X) = $FFE7 ; (via 812 ($32C)) close all files
asmsub UDTIM () clobbers(A,X) = $FFEA ; update the software clock
asmsub SCREEN () -> ubyte @ X, ubyte @ Y = $FFED ; read number of screen rows and columns
asmsub PLOT (ubyte dir @ Pc, ubyte col @ Y, ubyte row @ X) -> ubyte @ X, ubyte @ Y = $FFF0 ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
asmsub PLOT (ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y = $FFF0 ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
asmsub IOBASE () -> uword @ XY = $FFF3 ; read base address of I/O devices
; ---- end of C64 kernal routines ----

View File

@ -9,7 +9,7 @@
%import c64lib
~ c64utils {
c64utils {
const uword ESTACK_LO = $ce00
const uword ESTACK_HI = $cf00
@ -461,7 +461,7 @@ _raster_irq_handler
~ c64scr {
c64scr {
; ---- this block contains (character) Screen and text I/O related functions ----
@ -821,7 +821,7 @@ asmsub print_b (byte value @ A) clobbers(A,Y) {
}
asmsub print_ubhex (ubyte prefix @ Pc, ubyte value @ A) clobbers(A,Y) {
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 c64.SCRATCH_ZPREGX
@ -840,7 +840,7 @@ asmsub print_ubhex (ubyte prefix @ Pc, ubyte value @ A) clobbers(A,Y) {
}
asmsub print_ubbin (ubyte prefix @ Pc, ubyte value @ A) clobbers(A,Y) {
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 c64.SCRATCH_ZPREGX
@ -862,7 +862,7 @@ asmsub print_ubbin (ubyte prefix @ Pc, ubyte value @ A) clobbers(A,Y) {
}
asmsub print_uwbin (ubyte prefix @ Pc, uword value @ AY) clobbers(A,Y) {
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
@ -875,7 +875,7 @@ asmsub print_uwbin (ubyte prefix @ Pc, uword value @ AY) clobbers(A,Y) {
}
asmsub print_uwhex (ubyte prefix @ Pc, uword value @ AY) clobbers(A,Y) {
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 {{
@ -1063,7 +1063,7 @@ _mod lda $ffff ; modified
sub setcc (ubyte column, ubyte row, ubyte char, ubyte color) {
; ---- set char+color at the given position on the screen
%asm {{
lda setcc_row
lda row
asl a
tay
lda setchr._screenrows+1,y
@ -1072,15 +1072,15 @@ sub setcc (ubyte column, ubyte row, ubyte char, ubyte color) {
sta _colormod+2
lda setchr._screenrows,y
clc
adc setcc_column
adc column
sta _charmod+1
sta _colormod+1
bcc +
inc _charmod+2
inc _colormod+2
+ lda setcc_char
+ lda char
_charmod sta $ffff ; modified
lda setcc_color
lda color
_colormod sta $ffff ; modified
rts
}}

View File

@ -6,6 +6,6 @@
%import c64lib
~ math {
math {
%asminclude "library:math.asm", ""
}

View File

@ -36,6 +36,17 @@ init_system .proc
.pend
read_byte_from_address .proc
; -- read the byte from the memory address on the top of the stack, return in A (stack remains unchanged)
lda c64.ESTACK_LO+1,x
ldy c64.ESTACK_HI+1,x
sta (+) +1
sty (+) +2
+ lda $ffff ; modified
rts
.pend
add_a_to_zpword .proc
; -- add ubyte in A to the uword in c64.SCRATCH_ZPWORD1
clc
@ -840,11 +851,12 @@ func_all_w .proc
bne +
iny
lda (c64.SCRATCH_ZPWORD1),y
bne +
bne ++
lda #0
sta c64.ESTACK_LO+1,x
rts
+ iny
+ iny
_cmp_mod cpy #255 ; modified
bne -
lda #1

View File

@ -6,6 +6,6 @@
%import c64lib
~ prog8_lib {
prog8_lib {
%asminclude "library:prog8lib.asm", ""
}

View File

@ -1 +1 @@
1.20
1.51

View File

@ -1,21 +1,20 @@
package prog8
import prog8.ast.base.AstException
import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram
import prog8.parser.ParsingFailedError
import prog8.vm.astvm.AstVm
import prog8.vm.stackvm.stackVmMain
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardWatchEventKinds
import java.util.*
import kotlin.system.exitProcess
fun main(args: Array<String>) {
// check if the user wants to launch the VM instead
if("-vm" in args) {
val newArgs = args.toMutableList()
newArgs.remove("-vm")
return stackVmMain(newArgs.toTypedArray())
}
printSoftwareHeader("compiler")
if (args.isEmpty())
@ -33,54 +32,93 @@ internal fun printSoftwareHeader(what: String) {
private fun compileMain(args: Array<String>) {
var emulatorToStart = ""
var moduleFile = ""
var writeVmCode = false
var writeAssembly = true
var optimize = true
var optimizeInlining = true
var launchAstVm = false
var watchMode = false
for (arg in args) {
if(arg=="-emu")
emulatorToStart = "x64"
else if(arg=="-emu2")
emulatorToStart = "x64sc"
else if(arg=="-writevm")
writeVmCode = true
else if(arg=="-noasm")
writeAssembly = false
else if(arg=="-noopt")
optimize = false
else if(arg=="-nooptinline")
optimizeInlining = false
else if(arg=="-avm")
launchAstVm = true
else if(arg=="-watch")
watchMode = true
else if(!arg.startsWith("-"))
moduleFile = arg
else
usage()
}
if(moduleFile.isBlank())
usage()
val filepath = Paths.get(moduleFile).normalize()
if(watchMode) {
if(moduleFile.isBlank())
usage()
val (programAst, programName) = compileProgram(filepath, optimize, optimizeInlining,
!launchAstVm, writeVmCode, writeAssembly)
val watchservice = FileSystems.getDefault().newWatchService()
if(launchAstVm) {
println("\nLaunching AST-based vm...")
val vm = AstVm(programAst)
vm.run()
}
while(true) {
val filepath = Paths.get(moduleFile).normalize()
println("Continuous watch mode active. Main module: $filepath")
if(emulatorToStart.isNotEmpty()) {
if(programName==null)
println("\nCan't start emulator because no program was assembled.")
else {
println("\nStarting C-64 emulator $emulatorToStart...")
val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "$programName.vice-mon-list",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg")
val process = ProcessBuilder(cmdline).inheritIO().start()
process.waitFor()
try {
val compilationResult = compileProgram(filepath, optimize, writeAssembly)
println("Imported files (now watching:)")
for (importedFile in compilationResult.importedFiles) {
print(" ")
println(importedFile)
importedFile.parent.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
}
println("${Date()}: Waiting for file changes.")
val event = watchservice.take()
for(changed in event.pollEvents()) {
val changedPath = changed.context() as Path
println(" change detected: ${changedPath}")
}
event.reset()
println("\u001b[H\u001b[2J") // clear the screen
} catch (x: Exception) {
throw x
}
}
} else {
if(moduleFile.isBlank())
usage()
val filepath = Paths.get(moduleFile).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath, optimize, writeAssembly)
if(!compilationResult.success)
exitProcess(1)
} catch (x: ParsingFailedError) {
exitProcess(1)
} catch (x: AstException) {
exitProcess(1)
}
if (launchAstVm) {
println("\nLaunching AST-based vm...")
val vm = AstVm(compilationResult.programAst)
vm.run()
}
if (emulatorToStart.isNotEmpty()) {
if (compilationResult.programName.isEmpty())
println("\nCan't start emulator because no program was assembled.")
else {
println("\nStarting C-64 emulator $emulatorToStart...")
val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg")
val process = ProcessBuilder(cmdline).inheritIO().start()
process.waitFor()
}
}
}
}
@ -88,14 +126,12 @@ private fun compileMain(args: Array<String>) {
private fun usage() {
System.err.println("Missing argument(s):")
System.err.println(" [-noasm] don't create assembly code")
System.err.println(" [-noopt] don't perform any optimizations")
System.err.println(" [-emu] auto-start the 'x64' C-64 emulator after successful compilation")
System.err.println(" [-emu2] auto-start the 'x64sc' C-64 emulator after successful compilation")
System.err.println(" [-writevm] write intermediate vm code to a file as well")
System.err.println(" [-noasm] don't create assembly code")
System.err.println(" [-vm] launch the prog8 virtual machine instead of the compiler")
System.err.println(" [-avm] launch the prog8 ast-based virtual machine after compilation")
System.err.println(" [-noopt] don't perform any optimizations")
System.err.println(" [-nooptinline] don't perform subroutine inlining optimizations")
System.err.println(" [-watch] continuous compilation mode (watches for file changes)")
System.err.println(" modulefile main module file to compile")
exitProcess(1)
}

View File

@ -1,8 +1,5 @@
package prog8.compiler
package prog8.ast
import prog8.ast.IFunctionCall
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.antlr.escape
import prog8.ast.base.DataType
import prog8.ast.base.NumericDatatypes
@ -11,6 +8,7 @@ import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
import prog8.compiler.toHex
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
private var scopelevel = 0
@ -345,7 +343,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
override fun visit(repeatLoop: RepeatLoop) {
outputln("repeat ")
output("repeat ")
repeatLoop.body.accept(this)
output(" until ")
repeatLoop.untilCondition.accept(this)
@ -429,13 +427,14 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
override fun visit(whenChoice: WhenChoice) {
if(whenChoice.values==null)
val choiceValues = whenChoice.values
if(choiceValues==null)
outputi("else -> ")
else {
outputi("")
for(value in whenChoice.values) {
for(value in choiceValues) {
value.accept(this)
if(value !== whenChoice.values.last())
if(value !== choiceValues.last())
output(",")
}
output(" -> ")

View File

@ -168,9 +168,11 @@ class Program(val name: String, val modules: MutableList<Module>) {
val namespace = GlobalNamespace(modules)
val heap = HeapValues()
val loadAddress: Int
val definedLoadAddress: Int
get() = modules.first().loadAddress
var actualLoadAddress: Int = 0
fun entrypoint(): Subroutine? {
val mainBlocks = modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block }
if(mainBlocks.size > 1)
@ -181,6 +183,8 @@ class Program(val name: String, val modules: MutableList<Module>) {
mainBlocks[0].subScopes()["start"] as Subroutine?
}
}
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
}
class Module(override val name: String,
@ -236,7 +240,7 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
}
}
}
// lookup something from the module.
val stmt = localContext.definingModule().lookup(scopedName, localContext)
return when (stmt) {
is Label, is VarDecl, is Block, is Subroutine -> stmt

View File

@ -440,7 +440,7 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
litval.arrayliteral()!=null -> {
val array = litval.arrayliteral()?.toAst()
// the actual type of the arraysize can not yet be determined here (missing namespace & heap)
// the ConstantFolder takes care of that and converts the type if needed.
// the ConstantFold takes care of that and converts the type if needed.
ReferenceLiteralValue(DataType.ARRAY_UB, array = array, position = litval.toPosition())
}
litval.structliteral()!=null -> {

View File

@ -1,6 +1,7 @@
package prog8.ast.base
import prog8.ast.Node
import prog8.compiler.target.c64.MachineDefinition
/**************************** AST Data classes ****************************/
@ -25,10 +26,10 @@ enum class DataType {
infix fun isAssignableTo(targetType: DataType) =
// what types are assignable to others without loss of precision?
when(this) {
UBYTE -> targetType in setOf(UBYTE, UWORD, WORD, FLOAT)
BYTE -> targetType in setOf(BYTE, UBYTE, UWORD, WORD, FLOAT)
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
UWORD -> targetType in setOf(UWORD, FLOAT)
WORD -> targetType in setOf(WORD, UWORD, FLOAT)
WORD -> targetType in setOf(WORD, FLOAT)
FLOAT -> targetType == FLOAT
STR -> targetType == STR || targetType==STR_S
STR_S -> targetType == STR || targetType==STR_S
@ -52,6 +53,16 @@ enum class DataType {
in WordDatatypes -> other in WordDatatypes
else -> false
}
fun memorySize(): Int {
return when(this) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
FLOAT -> MachineDefinition.Mflpt5.MemorySize
in PassByReferenceDatatypes -> 2
else -> -9999999
}
}
}
enum class Register {
@ -111,6 +122,8 @@ val IterableDatatypes = setOf(
val PassByValueDatatypes = NumericDatatypes
val PassByReferenceDatatypes = IterableDatatypes.plus(DataType.STRUCT)
val ArrayElementTypes = mapOf(
DataType.STR to DataType.UBYTE,
DataType.STR_S to DataType.UBYTE,
DataType.ARRAY_B to DataType.BYTE,
DataType.ARRAY_UB to DataType.UBYTE,
DataType.ARRAY_W to DataType.WORD,

View File

@ -10,13 +10,13 @@ class SyntaxError(override var message: String, val position: Position) : AstExc
override fun toString() = "$position Syntax error: $message"
}
class NameError(override var message: String, val position: Position) : AstException(message) {
open class NameError(override var message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Name error: $message"
}
open class ExpressionError(message: String, val position: Position) : AstException(message) {
class ExpressionError(message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Error: $message"
}
class UndefinedSymbolError(symbol: IdentifierReference)
: ExpressionError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
: NameError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)

View File

@ -4,6 +4,7 @@ import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.processing.*
import prog8.compiler.CompilationOptions
import prog8.compiler.target.c64.codegen2.AnonymousScopeVarsCleanup
import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops
@ -11,10 +12,6 @@ import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops
internal const val initvarsSubName="prog8_init_vars"
// prefix for literal values that are turned into a variable on the heap
internal const val autoHeapValuePrefix = "auto_heap_value_"
internal fun Program.removeNopsFlattenAnonScopes() {
val flattener = FlattenAnonymousScopesAndRemoveNops()
flattener.visit(this)
@ -28,8 +25,15 @@ internal fun Program.checkValid(compilerOptions: CompilationOptions) {
}
internal fun Program.anonscopeVarsCleanup() {
val mover = AnonymousScopeVarsCleanup(this)
mover.visit(this)
printErrors(mover.result(), name)
}
internal fun Program.reorderStatements() {
val initvalueCreator = VarInitValueAndAddressOfCreator(namespace, heap)
val initvalueCreator = VarInitValueAndAddressOfCreator(this)
initvalueCreator.visit(this)
val checker = StatementReorderer(this)

View File

@ -18,6 +18,7 @@ import prog8.compiler.target.c64.Petscii
import prog8.functions.BuiltinFunctions
import prog8.functions.NotConstArgumentException
import prog8.functions.builtinFunctionReturnType
import java.util.*
import kotlin.math.abs
@ -28,7 +29,7 @@ sealed class Expression: Node {
abstract fun constValue(program: Program): NumericLiteralValue?
abstract fun accept(visitor: IAstModifyingVisitor): Expression
abstract fun accept(visitor: IAstVisitor)
abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this here and move it into CallGraph instead
abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this and add identifier usage tracking into CallGraph instead
abstract fun inferType(program: Program): DataType?
infix fun isSameAs(other: Expression): Boolean {
@ -97,14 +98,13 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
val leftDt = left.inferType(program)
val rightDt = right.inferType(program)
return when (operator) {
"+", "-", "*", "**", "%" -> if (leftDt == null || rightDt == null) null else {
"+", "-", "*", "**", "%", "/" -> if (leftDt == null || rightDt == null) null else {
try {
arithmeticOpDt(leftDt, rightDt)
commonDatatype(leftDt, rightDt, null, null).first
} catch (x: FatalAstException) {
null
}
}
"/" -> if (leftDt == null || rightDt == null) null else divisionOpDt(leftDt, rightDt)
"&" -> leftDt
"|" -> leftDt
"^" -> leftDt
@ -118,137 +118,66 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
}
companion object {
fun divisionOpDt(leftDt: DataType, rightDt: DataType): DataType {
fun commonDatatype(leftDt: DataType, rightDt: DataType,
left: Expression?, right: Expression?): Pair<DataType, Expression?> {
// byte + byte -> byte
// byte + word -> word
// word + byte -> word
// word + word -> word
// a combination with a float will be float (but give a warning about this!)
return when (leftDt) {
DataType.UBYTE -> when (rightDt) {
DataType.UBYTE, DataType.UWORD -> DataType.UBYTE
DataType.BYTE, DataType.WORD -> DataType.WORD
DataType.FLOAT -> DataType.BYTE
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
DataType.UBYTE -> {
when (rightDt) {
DataType.UBYTE -> Pair(DataType.UBYTE, null)
DataType.BYTE -> Pair(DataType.BYTE, left)
DataType.UWORD -> Pair(DataType.UWORD, left)
DataType.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> Pair(leftDt, null) // non-numeric datatype
}
}
DataType.BYTE -> when (rightDt) {
in NumericDatatypes -> DataType.BYTE
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
DataType.BYTE -> {
when (rightDt) {
DataType.UBYTE -> Pair(DataType.BYTE, right)
DataType.BYTE -> Pair(DataType.BYTE, null)
DataType.UWORD -> Pair(DataType.WORD, left)
DataType.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> Pair(leftDt, null) // non-numeric datatype
}
}
DataType.UWORD -> when (rightDt) {
DataType.UBYTE, DataType.UWORD -> DataType.UWORD
DataType.BYTE, DataType.WORD -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
DataType.UWORD -> {
when (rightDt) {
DataType.UBYTE -> Pair(DataType.UWORD, right)
DataType.BYTE -> Pair(DataType.WORD, right)
DataType.UWORD -> Pair(DataType.UWORD, null)
DataType.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> Pair(leftDt, null) // non-numeric datatype
}
}
DataType.WORD -> when (rightDt) {
in NumericDatatypes -> DataType.WORD
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
DataType.WORD -> {
when (rightDt) {
DataType.UBYTE -> Pair(DataType.WORD, right)
DataType.BYTE -> Pair(DataType.WORD, right)
DataType.UWORD -> Pair(DataType.WORD, right)
DataType.WORD -> Pair(DataType.WORD, null)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> Pair(leftDt, null) // non-numeric datatype
}
}
DataType.FLOAT -> when (rightDt) {
in NumericDatatypes -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
DataType.FLOAT -> {
Pair(DataType.FLOAT, right)
}
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
else -> Pair(leftDt, null) // non-numeric datatype
}
}
fun arithmeticOpDt(leftDt: DataType, rightDt: DataType): DataType {
return when (leftDt) {
DataType.UBYTE -> when (rightDt) {
DataType.UBYTE -> DataType.UBYTE
DataType.BYTE -> DataType.BYTE
DataType.UWORD -> DataType.UWORD
DataType.WORD -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.BYTE -> when (rightDt) {
in ByteDatatypes -> DataType.BYTE
in WordDatatypes -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.UWORD -> when (rightDt) {
DataType.UBYTE, DataType.UWORD -> DataType.UWORD
DataType.BYTE, DataType.WORD -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.WORD -> when (rightDt) {
in IntegerDatatypes -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
DataType.FLOAT -> when (rightDt) {
in NumericDatatypes -> DataType.FLOAT
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt")
}
}
}
fun commonDatatype(leftDt: DataType, rightDt: DataType,
left: Expression, right: Expression): Pair<DataType, Expression?> {
// byte + byte -> byte
// byte + word -> word
// word + byte -> word
// word + word -> word
// a combination with a float will be float (but give a warning about this!)
if(this.operator=="/") {
// division is a bit weird, don't cast the operands
val commondt = divisionOpDt(leftDt, rightDt)
return Pair(commondt, null)
}
return when (leftDt) {
DataType.UBYTE -> {
when (rightDt) {
DataType.UBYTE -> Pair(DataType.UBYTE, null)
DataType.BYTE -> Pair(DataType.BYTE, left)
DataType.UWORD -> Pair(DataType.UWORD, left)
DataType.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> Pair(leftDt, null) // non-numeric datatype
}
}
DataType.BYTE -> {
when (rightDt) {
DataType.UBYTE -> Pair(DataType.BYTE, right)
DataType.BYTE -> Pair(DataType.BYTE, null)
DataType.UWORD -> Pair(DataType.WORD, left)
DataType.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> Pair(leftDt, null) // non-numeric datatype
}
}
DataType.UWORD -> {
when (rightDt) {
DataType.UBYTE -> Pair(DataType.UWORD, right)
DataType.BYTE -> Pair(DataType.UWORD, right)
DataType.UWORD -> Pair(DataType.UWORD, null)
DataType.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> Pair(leftDt, null) // non-numeric datatype
}
}
DataType.WORD -> {
when (rightDt) {
DataType.UBYTE -> Pair(DataType.WORD, right)
DataType.BYTE -> Pair(DataType.WORD, right)
DataType.UWORD -> Pair(DataType.WORD, right)
DataType.WORD -> Pair(DataType.WORD, null)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> Pair(leftDt, null) // non-numeric datatype
}
}
DataType.FLOAT -> {
Pair(DataType.FLOAT, right)
}
else -> Pair(leftDt, null) // non-numeric datatype
}
}
}
class ArrayIndexedExpression(val identifier: IdentifierReference,
var arrayspec: ArrayIndex,
class ArrayIndexedExpression(var identifier: IdentifierReference,
val arrayspec: ArrayIndex,
override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@ -304,7 +233,7 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
}
}
data class AddressOf(val identifier: IdentifierReference, override val position: Position) : Expression() {
data class AddressOf(var identifier: IdentifierReference, override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@ -312,7 +241,6 @@ data class AddressOf(val identifier: IdentifierReference, override val position:
identifier.parent=this
}
var scopedname: String? = null // will be set in a later state by the compiler
override fun constValue(program: Program): NumericLiteralValue? = null
override fun referencesIdentifiers(vararg name: String) = false
override fun inferType(program: Program) = DataType.UWORD
@ -367,6 +295,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
in 0..255 -> NumericLiteralValue(DataType.UBYTE, value, position)
in -128..127 -> NumericLiteralValue(DataType.BYTE, value, position)
in 0..65535 -> NumericLiteralValue(DataType.UWORD, value, position)
in -32768..32767 -> NumericLiteralValue(DataType.WORD, value, position)
else -> throw FatalAstException("integer overflow: $value")
}
}
@ -388,7 +317,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
override fun inferType(program: Program) = type
override fun hashCode(): Int = type.hashCode() * 31 xor number.hashCode()
override fun hashCode(): Int = Objects.hash(type, number)
override fun equals(other: Any?): Boolean {
if(other==null || other !is NumericLiteralValue)
@ -398,7 +327,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble())
fun cast(targettype: DataType): NumericLiteralValue? {
fun cast(targettype: DataType): NumericLiteralValue {
if(type==targettype)
return this
val numval = number.toDouble()
@ -453,7 +382,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
}
else -> {}
}
return null // invalid type conversion from $this to $targettype
throw ExpressionError("can't cast $type into $targettype", position)
}
}
@ -495,12 +424,12 @@ class ReferenceLiteralValue(val type: DataType, // only reference types allo
init {
when(type){
in StringDatatypes ->
if(str==null && heapId==null) throw FatalAstException("literal value missing strvalue/heapId")
if(str==null) throw FatalAstException("literal value missing strvalue/heapId")
in ArrayDatatypes ->
if(array==null && heapId==null) throw FatalAstException("literal value missing arrayvalue/heapId")
if(array==null) throw FatalAstException("literal value missing arrayvalue/heapId")
else -> throw FatalAstException("invalid type $type")
}
if(array==null && str==null && heapId==null)
if(array==null && str==null)
throw FatalAstException("literal ref value without actual value")
}
@ -524,16 +453,12 @@ class ReferenceLiteralValue(val type: DataType, // only reference types allo
in ArrayDatatypes -> "$array"
else -> throw FatalAstException("weird ref type")
}
return "ReferenceValueLiteral($type, $valueStr)"
return "RefValueLit($type, $valueStr)"
}
override fun inferType(program: Program) = type
override fun hashCode(): Int {
val sh = str?.hashCode() ?: 0x00014567
val ah = array?.hashCode() ?: 0x11119876
return sh * 31 xor ah xor type.hashCode()
}
override fun hashCode(): Int = Objects.hash(str, array, type)
override fun equals(other: Any?): Boolean {
if(other==null || other !is ReferenceLiteralValue)
@ -559,7 +484,24 @@ class ReferenceLiteralValue(val type: DataType, // only reference types allo
when(type) {
in StringDatatypes -> {
if(targettype in StringDatatypes)
return ReferenceLiteralValue(targettype, str, initHeapId = heapId, position = position)
return ReferenceLiteralValue(targettype, str, position = position)
}
in ArrayDatatypes -> {
if(targettype in ArrayDatatypes) {
val elementType = ArrayElementTypes.getValue(targettype)
val castArray = array!!.map{
val num = it as? NumericLiteralValue
if(num==null) {
// an array of UWORDs could possibly also contain AddressOfs
if (elementType != DataType.UWORD || it !is AddressOf)
throw FatalAstException("weird array element $it")
it
} else {
num.cast(elementType) // TODO this can throw an exception
}
}.toTypedArray()
return ReferenceLiteralValue(targettype, null, array=castArray, position = position)
}
}
else -> {}
}
@ -582,13 +524,15 @@ class ReferenceLiteralValue(val type: DataType, // only reference types allo
}
heapId = heap.addIntegerArray(type, intArrayWithAddressOfs.toTypedArray())
} else {
val valuesInArray = array.map { (it as NumericLiteralValue).number }
heapId = if(type== DataType.ARRAY_F) {
val doubleArray = valuesInArray.map { it.toDouble() }.toDoubleArray()
heap.addDoublesArray(doubleArray)
} else {
val integerArray = valuesInArray.map { it.toInt() }
heap.addIntegerArray(type, integerArray.map { IntegerOrAddressOf(it, null) }.toTypedArray())
val valuesInArray = array.map { (it as? NumericLiteralValue)?.number }
if(null !in valuesInArray) {
heapId = if (type == DataType.ARRAY_F) {
val doubleArray = valuesInArray.map { it!!.toDouble() }.toDoubleArray()
heap.addDoublesArray(doubleArray)
} else {
val integerArray = valuesInArray.map { it!!.toInt() }
heap.addIntegerArray(type, integerArray.map { IntegerOrAddressOf(it, null) }.toTypedArray())
}
}
}
}
@ -617,13 +561,13 @@ class RangeExpr(var from: Expression,
val toDt=to.inferType(program)
return when {
fromDt==null || toDt==null -> null
fromDt== DataType.UBYTE && toDt== DataType.UBYTE -> DataType.UBYTE
fromDt== DataType.UWORD && toDt== DataType.UWORD -> DataType.UWORD
fromDt== DataType.UBYTE && toDt== DataType.UBYTE -> DataType.ARRAY_UB
fromDt== DataType.UWORD && toDt== DataType.UWORD -> DataType.ARRAY_UW
fromDt== DataType.STR && toDt== DataType.STR -> DataType.STR
fromDt== DataType.STR_S && toDt== DataType.STR_S -> DataType.STR_S
fromDt== DataType.WORD || toDt== DataType.WORD -> DataType.WORD
fromDt== DataType.BYTE || toDt== DataType.BYTE -> DataType.BYTE
else -> DataType.UBYTE
fromDt== DataType.WORD || toDt== DataType.WORD -> DataType.ARRAY_W
fromDt== DataType.BYTE || toDt== DataType.BYTE -> DataType.ARRAY_B
else -> DataType.ARRAY_UB
}
}
override fun toString(): String {
@ -735,6 +679,8 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
}
}
fun memberOfStruct(namespace: INameScope) = this.targetVarDecl(namespace)?.struct
fun heapId(namespace: INameScope): Int {
val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolError(this)
val value = (node as? VarDecl)?.value ?: throw FatalAstException("requires a reference value")
@ -744,6 +690,13 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
else -> throw FatalAstException("requires a reference value")
}
}
fun withPrefixedName(nameprefix: String): IdentifierReference {
val prefixed = nameInSource.dropLast(1) + listOf(nameprefix+nameInSource.last())
val new = IdentifierReference(prefixed, position)
new.parent = parent
return new
}
}
class FunctionCall(override var target: IdentifierReference,

View File

@ -104,6 +104,12 @@ internal class AstChecker(private val program: Program,
super.visit(returnStmt)
}
override fun visit(ifStatement: IfStatement) {
if(ifStatement.condition.inferType(program) !in IntegerDatatypes)
checkResult.add(ExpressionError("condition value should be an integer type", ifStatement.condition.position))
super.visit(ifStatement)
}
override fun visit(forLoop: ForLoop) {
if(forLoop.body.containsNoCodeNorVars())
printWarning("for loop body is empty", forLoop.position)
@ -113,10 +119,11 @@ internal class AstChecker(private val program: Program,
checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position))
} else {
if (forLoop.loopRegister != null) {
printWarning("using a register as loop variable is risky (it could get clobbered)", forLoop.position)
// loop register
if (iterableDt != DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt !in StringDatatypes)
if (iterableDt != DataType.ARRAY_UB && iterableDt != DataType.ARRAY_B && iterableDt !in StringDatatypes)
checkResult.add(ExpressionError("register can only loop over bytes", forLoop.position))
if(forLoop.loopRegister!=Register.A)
checkResult.add(ExpressionError("it's only possible to use A as a loop register", forLoop.position))
} else {
// loop variable
val loopvar = forLoop.loopVar!!.targetVarDecl(program.namespace)
@ -143,14 +150,14 @@ internal class AstChecker(private val program: Program,
checkResult.add(ExpressionError("word loop variable can only loop over bytes or words", forLoop.position))
}
DataType.FLOAT -> {
if(iterableDt!= DataType.FLOAT && iterableDt != DataType.ARRAY_F)
checkResult.add(ExpressionError("float loop variable can only loop over floats", forLoop.position))
checkResult.add(ExpressionError("for loop only supports integers", forLoop.position))
}
else -> checkResult.add(ExpressionError("loop variable must be numeric type", forLoop.position))
}
}
}
}
super.visit(forLoop)
}
@ -297,15 +304,24 @@ internal class AstChecker(private val program: Program,
if(subroutine.asmClobbers.intersect(regCounts.keys).isNotEmpty())
err("a return register is also in the clobber list")
if(subroutine.statements.any{it !is InlineAssembly})
err("asmsub can only contain inline assembly (%asm)")
val statusFlagsNoCarry = subroutine.asmParameterRegisters.mapNotNull { it.statusflag }.toSet() - Statusflag.Pc
if(statusFlagsNoCarry.isNotEmpty())
err("can only use Carry as status flag parameter")
val carryParameter = subroutine.asmParameterRegisters.singleOrNull { it.statusflag==Statusflag.Pc }
if(carryParameter!=null && carryParameter !== subroutine.asmParameterRegisters.last())
err("carry parameter has to come last")
} else {
// TODO: non-asm subroutines can only take numeric arguments for now. (not strings and arrays) Maybe this can be improved now that we have '&' ?
// the way string params are treated is almost okay (their address is passed) but the receiving subroutine treats it as an integer rather than referring back to the original string.
// the way array params are treated is buggy; it thinks the subroutine needs a byte parameter in place of a byte[] ...
// This is not easy to fix because strings and arrays are treated a bit simplistic (a "virtual" pointer to the value on the heap)
// while passing them as subroutine parameters would require a "real" pointer OR copying the VALUE to the subroutine's parameter variable (which is very inefficient).
// For now, don't pass strings and arrays as parameters and instead create the workaround as suggested in the error message below.
if(!subroutine.parameters.all{it.type in NumericDatatypes }) {
err("Non-asm subroutine can only take numerical parameters (no str/array types) for now. Workaround (for nested subroutine): access the variable from the outer scope directly.")
// Pass-by-reference datatypes can not occur as parameters to a subroutine directly
// Instead, their reference (address) should be passed (as an UWORD).
// The language has no typed pointers at this time.
if(subroutine.parameters.any{it.type in PassByReferenceDatatypes }) {
err("Pass-by-reference types (str, array) cannot occur as a parameter type directly. Instead, use an uword for their address, or access the variable from the outer scope directly.")
}
}
}
@ -313,12 +329,16 @@ internal class AstChecker(private val program: Program,
override fun visit(repeatLoop: RepeatLoop) {
if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y"))
printWarning("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position)
if(repeatLoop.untilCondition.inferType(program) !in IntegerDatatypes)
checkResult.add(ExpressionError("condition value should be an integer type", repeatLoop.untilCondition.position))
super.visit(repeatLoop)
}
override fun visit(whileLoop: WhileLoop) {
if(whileLoop.condition.referencesIdentifiers("A", "X", "Y"))
printWarning("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position)
if(whileLoop.condition.inferType(program) !in IntegerDatatypes)
checkResult.add(ExpressionError("condition value should be an integer type", whileLoop.condition.position))
super.visit(whileLoop)
}
@ -353,6 +373,8 @@ internal class AstChecker(private val program: Program,
}
override fun visit(assignTarget: AssignTarget) {
super.visit(assignTarget)
val memAddr = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.number?.toInt()
if (memAddr != null) {
if (memAddr < 0 || memAddr >= 65536)
@ -360,12 +382,13 @@ internal class AstChecker(private val program: Program,
}
val assignment = assignTarget.parent as Statement
if (assignTarget.identifier != null) {
val targetName = assignTarget.identifier.nameInSource
val targetIdentifier = assignTarget.identifier
if (targetIdentifier != null) {
val targetName = targetIdentifier.nameInSource
val targetSymbol = program.namespace.lookup(targetName, assignment)
when (targetSymbol) {
null -> {
checkResult.add(ExpressionError("undefined symbol: ${targetName.joinToString(".")}", assignment.position))
checkResult.add(UndefinedSymbolError(targetIdentifier))
return
}
!is VarDecl -> {
@ -380,6 +403,9 @@ internal class AstChecker(private val program: Program,
}
}
}
val targetDt = assignTarget.inferType(program, assignment)
if(targetDt in StringDatatypes || targetDt in ArrayDatatypes)
checkResult.add(SyntaxError("cannot assign to a string or array type", assignTarget.position))
if (assignment is Assignment) {
@ -416,8 +442,6 @@ internal class AstChecker(private val program: Program,
if(variable.datatype !in ArrayDatatypes && variable.datatype !in StringDatatypes && variable.datatype!=DataType.STRUCT)
checkResult.add(ExpressionError("invalid pointer-of operand type", addressOf.position))
}
if(addressOf.scopedname==null)
throw FatalAstException("the scopedname of AddressOf should have been set by now $addressOf")
super.visit(addressOf)
}
@ -596,8 +620,9 @@ internal class AstChecker(private val program: Program,
directive.args[0].name != "basicsafe" &&
directive.args[0].name != "floatsafe" &&
directive.args[0].name != "kernalsafe" &&
directive.args[0].name != "dontuse" &&
directive.args[0].name != "full")
err("invalid zp type, expected basicsafe, floatsafe, kernalsafe, or full")
err("invalid zp type, expected basicsafe, floatsafe, kernalsafe, dontuse, or full")
}
"%zpreserved" -> {
if(directive.parent !is Module) err("this directive may only occur at module level")
@ -631,11 +656,11 @@ internal class AstChecker(private val program: Program,
if(directive.parent !is INameScope || directive.parent is Module) err("this directive may only occur in a block")
val errormsg = "invalid asmbinary directive, expected arguments: \"filename\" [, offset [, length ] ]"
if(directive.args.isEmpty()) err(errormsg)
if(directive.args.isNotEmpty() && directive.args[0].str==null) err(errormsg)
if(directive.args.size>=2 && directive.args[1].int==null) err(errormsg)
if(directive.args.size==3 && directive.args[2].int==null) err(errormsg)
if(directive.args.size>3) err(errormsg)
checkFileExists(directive, directive.args[0].str!!)
else if(directive.args.isNotEmpty() && directive.args[0].str==null) err(errormsg)
else if(directive.args.size>=2 && directive.args[1].int==null) err(errormsg)
else if(directive.args.size==3 && directive.args[2].int==null) err(errormsg)
else if(directive.args.size>3) err(errormsg)
else checkFileExists(directive, directive.args[0].str!!)
}
"%option" -> {
if(directive.parent !is Block && directive.parent !is Module) err("this directive may only occur in a block or at module level")
@ -726,12 +751,20 @@ internal class AstChecker(private val program: Program,
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
checkResult.add(ExpressionError("bitwise operator can only be used on integer operands", expr.right.position))
}
"<<", ">>" -> {
// for now, bit-shifts can only shift by a constant number
val constRight = expr.right.constValue(program)
if(constRight==null)
checkResult.add(ExpressionError("bit-shift can only be done by a constant number (for now)", expr.right.position))
}
}
if(leftDt !in NumericDatatypes)
checkResult.add(ExpressionError("left operand is not numeric", expr.left.position))
if(rightDt!in NumericDatatypes)
checkResult.add(ExpressionError("right operand is not numeric", expr.right.position))
if(leftDt!=rightDt)
checkResult.add(ExpressionError("left and right operands aren't the same type", expr.left.position))
super.visit(expr)
}
@ -748,8 +781,11 @@ internal class AstChecker(private val program: Program,
super.visit(range)
val from = range.from.constValue(program)
val to = range.to.constValue(program)
val stepLv = range.step.constValue(program) ?: NumericLiteralValue(DataType.UBYTE, 1, range.position)
if (stepLv.type !in IntegerDatatypes || stepLv.number.toInt() == 0) {
val stepLv = range.step.constValue(program)
if(stepLv==null) {
err("range step must be a constant integer")
return
} else if (stepLv.type !in IntegerDatatypes || stepLv.number.toInt() == 0) {
err("range step must be an integer != 0")
return
}
@ -792,6 +828,13 @@ internal class AstChecker(private val program: Program,
else
printWarning("result values of subroutine call are discarded", functionCallStatement.position)
}
if(functionCallStatement.target.nameInSource.last() in setOf("lsl", "lsr", "rol", "ror", "rol2", "ror2", "swap")) {
// in-place modification, can't be done on literals
if(functionCallStatement.arglist.any { it !is IdentifierReference && it !is RegisterExpr && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
checkResult.add(ExpressionError("can't use that as argument to a in-place modifying function", functionCallStatement.position))
}
}
super.visit(functionCallStatement)
}
@ -805,14 +848,17 @@ internal class AstChecker(private val program: Program,
if(args.size!=func.parameters.size)
checkResult.add(SyntaxError("invalid number of arguments", position))
else {
val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
for (arg in args.withIndex().zip(func.parameters)) {
val argDt=arg.first.value.inferType(program)
if(argDt!=null && !(argDt isAssignableTo arg.second.possibleDatatypes)) {
if (argDt != null
&& !(argDt isAssignableTo arg.second.possibleDatatypes)
&& (argDt != DataType.UWORD || arg.second.possibleDatatypes.intersect(paramTypesForAddressOf).isEmpty())) {
checkResult.add(ExpressionError("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position))
}
}
if(target.name=="swap") {
// swap() is a bit weird because this one is translated into a sequence of bytecodes, instead of being an actual function call
// swap() is a bit weird because this one is translated into a operations directly, instead of being a function call
val dt1 = args[0].inferType(program)!!
val dt2 = args[1].inferType(program)!!
if (dt1 != dt2)
@ -824,6 +870,14 @@ internal class AstChecker(private val program: Program,
else if(dt1 !in NumericDatatypes)
checkResult.add(ExpressionError("swap requires args of numerical type", position))
}
else if(target.name=="all" || target.name=="any") {
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype in StringDatatypes) {
checkResult.add(ExpressionError("any/all on a string is useless (is always true unless the string is empty)", position))
}
if(args[0].inferType(program) in StringDatatypes) {
checkResult.add(ExpressionError("any/all on a string is useless (is always true unless the string is empty)", position))
}
}
}
} else if(target is Subroutine) {
if(args.size!=target.parameters.size)
@ -866,7 +920,7 @@ internal class AstChecker(private val program: Program,
val targetName = postIncrDecr.target.identifier!!.nameInSource
val target = program.namespace.lookup(targetName, postIncrDecr)
if(target==null) {
checkResult.add(SyntaxError("undefined symbol: ${targetName.joinToString(".")}", postIncrDecr.position))
checkResult.add(UndefinedSymbolError(postIncrDecr.target.identifier!!))
} else {
if(target !is VarDecl || target.type== VarDeclType.CONST) {
checkResult.add(SyntaxError("can only increment or decrement a variable", postIncrDecr.position))
@ -877,7 +931,7 @@ internal class AstChecker(private val program: Program,
} else if(postIncrDecr.target.arrayindexed != null) {
val target = postIncrDecr.target.arrayindexed?.identifier?.targetStatement(program.namespace)
if(target==null) {
checkResult.add(SyntaxError("undefined symbol", postIncrDecr.position))
checkResult.add(NameError("undefined symbol", postIncrDecr.position))
}
else {
val dt = (target as VarDecl).datatype
@ -902,12 +956,14 @@ internal class AstChecker(private val program: Program,
if(index!=null && (index<0 || index>=arraysize))
checkResult.add(ExpressionError("array index out of bounds", arrayIndexedExpression.arrayspec.position))
} else if(target.datatype in StringDatatypes) {
// check string lengths
val heapId = (target.value as ReferenceLiteralValue).heapId!!
val stringLen = program.heap.get(heapId).str!!.length
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
if(index!=null && (index<0 || index>=stringLen))
checkResult.add(ExpressionError("index out of bounds", arrayIndexedExpression.arrayspec.position))
if(target.value is ReferenceLiteralValue) {
// check string lengths for non-memory mapped strings
val heapId = (target.value as ReferenceLiteralValue).heapId!!
val stringLen = program.heap.get(heapId).str!!.length
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
if (index != null && (index < 0 || index >= stringLen))
checkResult.add(ExpressionError("index out of bounds", arrayIndexedExpression.arrayspec.position))
}
}
} else
checkResult.add(SyntaxError("indexing requires a variable to act upon", arrayIndexedExpression.position))
@ -930,6 +986,9 @@ internal class AstChecker(private val program: Program,
tally.filter { it.value>1 }.forEach {
checkResult.add(SyntaxError("choice value occurs multiple times", it.key.position))
}
if(whenStatement.choices.isEmpty())
checkResult.add(SyntaxError("empty when statement", whenStatement.position))
super.visit(whenStatement)
}
@ -937,7 +996,7 @@ internal class AstChecker(private val program: Program,
val whenStmt = whenChoice.parent as WhenStatement
if(whenChoice.values!=null) {
val conditionType = whenStmt.condition.inferType(program)
val constvalues = whenChoice.values.map { it.constValue(program) }
val constvalues = whenChoice.values!!.map { it.constValue(program) }
for(constvalue in constvalues) {
when {
constvalue == null -> checkResult.add(SyntaxError("choice value must be a constant", whenChoice.position))

View File

@ -7,10 +7,13 @@ import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.AssemblyProgram
import prog8.functions.BuiltinFunctions
internal class AstIdentifiersChecker(private val program: Program) : IAstModifyingVisitor {
private val checkResult: MutableList<AstException> = mutableListOf()
private var blocks = mutableMapOf<String, Block>()
@ -64,6 +67,9 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", decl.position))
if(decl.name in AssemblyProgram.opcodeNames)
checkResult.add(NameError("can't use a cpu opcode name as a symbol", decl.position))
// is it a struct variable? then define all its struct members as mangled names,
// and include the original decl as well.
if(decl.datatype==DataType.STRUCT) {
@ -98,17 +104,28 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
}
override fun visit(subroutine: Subroutine): Statement {
if(subroutine.name in BuiltinFunctions) {
if(subroutine.name in AssemblyProgram.opcodeNames) {
checkResult.add(NameError("can't use a cpu opcode name as a symbol", subroutine.position))
} else if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
} else {
if (subroutine.parameters.any { it.name in BuiltinFunctions })
checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
// already reported elsewhere:
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
// checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
val existing = program.namespace.lookup(listOf(subroutine.name), subroutine)
if (existing != null && existing !== subroutine)
nameError(subroutine.name, subroutine.position, existing)
// does the parameter redefine a variable declared elsewhere?
for(param in subroutine.parameters) {
val existingVar = subroutine.lookup(listOf(param.name), subroutine)
if (existingVar != null && existingVar.parent !== subroutine) {
nameError(param.name, param.position, existingVar)
}
}
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters
val symbolsInSub = subroutine.allDefinedSymbols()
val namesInSub = symbolsInSub.map{ it.first }.toSet()
@ -133,7 +150,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
subroutine.parameters
.filter { it.name !in namesInSub }
.forEach {
val vardecl = VarDecl(VarDeclType.VAR, it.type, ZeropageWish.DONTCARE, null, it.name, null, null,
val vardecl = VarDecl(VarDeclType.VAR, it.type, ZeropageWish.NOT_IN_ZEROPAGE, null, it.name, null, null,
isArray = false, autogeneratedDontRemove = true, position = subroutine.position)
vardecl.linkParents(subroutine)
subroutine.statements.add(0, vardecl)
@ -145,6 +162,9 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
}
override fun visit(label: Label): Statement {
if(label.name in AssemblyProgram.opcodeNames)
checkResult.add(NameError("can't use a cpu opcode name as a symbol", label.position))
if(label.name in BuiltinFunctions) {
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", label.position))
@ -166,30 +186,36 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
checkResult.add(SyntaxError("register loop variables have a fixed implicit datatype", forLoop.position))
if(forLoop.loopRegister == Register.X)
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
} else if(forLoop.loopVar!=null) {
val varName = forLoop.loopVar.nameInSource.last()
if(forLoop.decltype!=null) {
val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first())
if(existing==null) {
// create the local scoped for loop variable itself
val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, forLoop.zeropage, null, varName, null, null,
isArray = false, autogeneratedDontRemove = true, position = forLoop.loopVar.position)
vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl)
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
} else {
val loopVar = forLoop.loopVar
if (loopVar != null) {
val varName = loopVar.nameInSource.last()
if (forLoop.decltype != null) {
val existing = if (forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(loopVar.nameInSource, forLoop.body.statements.first())
if (existing == null) {
// create the local scoped for loop variable itself
val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, forLoop.zeropage, null, varName, null, null,
isArray = false, autogeneratedDontRemove = true, position = loopVar.position)
vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl)
loopVar.parent = forLoop.body // loopvar 'is defined in the body'
} else if(existing.parent!==forLoop && existing.parent.parent!==forLoop) {
checkResult.add(NameError("for loop var was already defined at ${existing.position}", loopVar.position))
}
}
}
if(forLoop.iterable !is RangeExpr) {
val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(ForLoop.iteratorLoopcounterVarname), forLoop.body.statements.first())
if(existing==null) {
// create loop iteration counter variable (without value, to avoid an assignment)
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE, null, ForLoop.iteratorLoopcounterVarname, null, null,
isArray = false, autogeneratedDontRemove = true, position = forLoop.loopVar.position)
vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl)
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
val validName = forLoop.body.name.replace("<", "").replace(">", "").replace("-", "")
val loopvarName = "prog8_loopvar_$validName"
if (forLoop.iterable !is RangeExpr) {
val existing = if (forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(loopvarName), forLoop.body.statements.first())
if (existing == null) {
// create loop iteration counter variable (without value, to avoid an assignment)
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE, null, loopvarName, null, null,
isArray = false, autogeneratedDontRemove = true, position = loopVar.position)
vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl)
loopVar.parent = forLoop.body // loopvar 'is defined in the body'
}
}
}
}
@ -211,8 +237,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
val newValue: Expression
val lval = returnStmt.value as? NumericLiteralValue
if(lval!=null) {
val adjusted = lval.cast(subroutine.returntypes.single())
newValue = if(adjusted!=null && adjusted !== lval) adjusted else lval
newValue = lval.cast(subroutine.returntypes.single())
} else {
newValue = returnStmt.value!!
}
@ -223,18 +248,64 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
}
override fun visit(refLiteral: ReferenceLiteralValue): Expression {
if(refLiteral.parent !is VarDecl) {
return makeIdentifierFromRefLv(refLiteral)
val litval = super.visit(refLiteral)
if(litval is ReferenceLiteralValue) {
val vardecl = litval.parent as? VarDecl
if (litval.isString) {
// intern the string; move it into the heap
if (litval.str!!.length !in 1..255)
checkResult.add(ExpressionError("string literal length must be between 1 and 255", litval.position))
else {
litval.addToHeap(program.heap)
}
return if(vardecl!=null)
litval
else
makeIdentifierFromRefLv(litval) // replace the literal string by a identifier reference.
} else if (litval.isArray) {
if (vardecl!=null) {
return fixupArrayDatatype(litval, vardecl, program.heap)
} else {
// fix the datatype of the array (also on the heap) to the 'biggest' datatype in the array
// (we don't know the desired datatype here exactly so we guess)
val datatype = determineArrayDt(litval.array!!) ?: return litval
val litval2 = litval.cast(datatype)!!
litval2.parent = litval.parent
// finally, replace the literal array by a identifier reference.
return makeIdentifierFromRefLv(litval2)
}
}
}
return super.visit(refLiteral)
return litval
}
private fun determineArrayDt(array: Array<Expression>): DataType? {
val datatypesInArray = array.mapNotNull { it.inferType(program) }
if(datatypesInArray.isEmpty())
return null
if(DataType.FLOAT in datatypesInArray)
return DataType.ARRAY_F
if(DataType.WORD in datatypesInArray)
return DataType.ARRAY_W
if(DataType.UWORD in datatypesInArray)
return DataType.ARRAY_UW
if(DataType.BYTE in datatypesInArray)
return DataType.ARRAY_B
if(DataType.UBYTE in datatypesInArray)
return DataType.ARRAY_UB
return null
}
private fun makeIdentifierFromRefLv(refLiteral: ReferenceLiteralValue): IdentifierReference {
// a referencetype literal value that's not declared as a variable
// we need to introduce an auto-generated variable for this to be able to refer to the value
// note: if the var references the same literal value, it is not yet de-duplicated here.
refLiteral.addToHeap(program.heap)
val variable = VarDecl.createAuto(refLiteral, program.heap)
addVarDecl(refLiteral.definingScope(), variable)
val scope = refLiteral.definingScope()
var variable = VarDecl.createAuto(refLiteral, program.heap)
val existing = scope.lookup(listOf(variable.name), refLiteral)
variable = addVarDecl(scope, variable)
// replace the reference literal by a identifier reference
val identifier = IdentifierReference(listOf(variable.name), variable.position)
identifier.parent = refLiteral.parent
@ -244,7 +315,6 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
override fun visit(addressOf: AddressOf): Expression {
// register the scoped name of the referenced identifier
val variable= addressOf.identifier.targetVarDecl(program.namespace) ?: return addressOf
addressOf.scopedname = variable.scopedname
return super.visit(addressOf)
}
@ -290,12 +360,41 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
return expr
}
private fun addVarDecl(scope: INameScope, variable: VarDecl) {
private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl {
if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableListOf()
val declList = vardeclsToAdd.getValue(scope)
if(declList.all{it.name!=variable.name})
val existing = declList.singleOrNull { it.name==variable.name }
return if(existing!=null) {
existing
} else {
declList.add(variable)
variable
}
}
}
internal fun fixupArrayDatatype(array: ReferenceLiteralValue, vardecl: VarDecl, heap: HeapValues): ReferenceLiteralValue {
if(array.heapId!=null) {
val arrayDt = array.type
if(arrayDt!=vardecl.datatype) {
// fix the datatype of the array (also on the heap) to match the vardecl
val litval2 =
try {
array.cast(vardecl.datatype)!!
} catch(x: ExpressionError) {
// couldn't cast permanently.
// instead, simply adjust the array type and trust the AstChecker to report the exact error
ReferenceLiteralValue(vardecl.datatype, null, array.array, array.heapId, array.position)
}
vardecl.value = litval2
litval2.linkParents(vardecl)
litval2.addToHeap(heap) // TODO is the previous array discarded from the resulting asm code?
return litval2
}
} else {
array.addToHeap(heap)
}
return array
}

View File

@ -2,6 +2,7 @@ package prog8.ast.processing
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
@ -11,7 +12,7 @@ interface IAstModifyingVisitor {
}
fun visit(module: Module) {
module.statements = module.statements.asSequence().map { it.accept(this) }.toMutableList()
module.statements = module.statements.map { it.accept(this) }.toMutableList()
}
fun visit(expr: PrefixExpression): Expression {
@ -30,7 +31,7 @@ interface IAstModifyingVisitor {
}
fun visit(block: Block): Statement {
block.statements = block.statements.asSequence().map { it.accept(this) }.toMutableList()
block.statements = block.statements.map { it.accept(this) }.toMutableList()
return block
}
@ -41,7 +42,7 @@ interface IAstModifyingVisitor {
}
fun visit(subroutine: Subroutine): Statement {
subroutine.statements = subroutine.statements.asSequence().map { it.accept(this) }.toMutableList()
subroutine.statements = subroutine.statements.map { it.accept(this) }.toMutableList()
return subroutine
}
@ -49,6 +50,8 @@ interface IAstModifyingVisitor {
val newtarget = functionCall.target.accept(this)
if(newtarget is IdentifierReference)
functionCall.target = newtarget
else
throw FatalAstException("cannot change class of function call target")
functionCall.arglist = functionCall.arglist.map { it.accept(this) }.toMutableList()
return functionCall
}
@ -57,6 +60,8 @@ interface IAstModifyingVisitor {
val newtarget = functionCallStatement.target.accept(this)
if(newtarget is IdentifierReference)
functionCallStatement.target = newtarget
else
throw FatalAstException("cannot change class of function call target")
functionCallStatement.arglist = functionCallStatement.arglist.map { it.accept(this) }.toMutableList()
return functionCallStatement
}
@ -135,7 +140,12 @@ interface IAstModifyingVisitor {
}
fun visit(forLoop: ForLoop): Statement {
forLoop.loopVar?.accept(this)
val newloopvar = forLoop.loopVar?.accept(this)
when(newloopvar) {
is IdentifierReference -> forLoop.loopVar = newloopvar
null -> forLoop.loopVar = null
else -> throw FatalAstException("can't change class of loopvar")
}
forLoop.iterable = forLoop.iterable.accept(this)
forLoop.body = forLoop.body.accept(this) as AnonymousScope
return forLoop
@ -158,21 +168,28 @@ interface IAstModifyingVisitor {
return returnStmt
}
fun visit(arrayIndexedExpression: ArrayIndexedExpression): Expression {
arrayIndexedExpression.identifier.accept(this)
fun visit(arrayIndexedExpression: ArrayIndexedExpression): ArrayIndexedExpression {
val ident = arrayIndexedExpression.identifier.accept(this)
if(ident is IdentifierReference)
arrayIndexedExpression.identifier = ident
arrayIndexedExpression.arrayspec.accept(this)
return arrayIndexedExpression
}
fun visit(assignTarget: AssignTarget): AssignTarget {
assignTarget.arrayindexed?.accept(this)
assignTarget.identifier?.accept(this)
val ident = assignTarget.identifier?.accept(this)
when (ident) {
is IdentifierReference -> assignTarget.identifier = ident
null -> assignTarget.identifier = null
else -> throw FatalAstException("can't change class of assign target identifier")
}
assignTarget.arrayindexed = assignTarget.arrayindexed?.accept(this)
assignTarget.memoryAddress?.let { visit(it) }
return assignTarget
}
fun visit(scope: AnonymousScope): Statement {
scope.statements = scope.statements.asSequence().map { it.accept(this) }.toMutableList()
scope.statements = scope.statements.map { it.accept(this) }.toMutableList()
return scope
}
@ -191,7 +208,11 @@ interface IAstModifyingVisitor {
}
fun visit(addressOf: AddressOf): Expression {
addressOf.identifier.accept(this)
val ident = addressOf.identifier.accept(this)
if(ident is IdentifierReference)
addressOf.identifier = ident
else
throw FatalAstException("can't change class of addressof identifier")
return addressOf
}
@ -212,14 +233,20 @@ interface IAstModifyingVisitor {
}
fun visit(whenStatement: WhenStatement): Statement {
whenStatement.condition.accept(this)
whenStatement.condition = whenStatement.condition.accept(this)
whenStatement.choices.forEach { it.accept(this) }
return whenStatement
}
fun visit(whenChoice: WhenChoice) {
whenChoice.values?.forEach { it.accept(this) }
whenChoice.statements.accept(this)
whenChoice.values = whenChoice.values?.map { it.accept(this) }
val stmt = whenChoice.statements.accept(this)
if(stmt is AnonymousScope)
whenChoice.statements = stmt
else {
whenChoice.statements = AnonymousScope(mutableListOf(stmt), stmt.position)
whenChoice.statements.linkParents(whenChoice)
}
}
fun visit(structDecl: StructDecl): Statement {

View File

@ -187,26 +187,29 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
}
override fun visit(expr: BinaryExpression): Expression {
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
val expr2 = super.visit(expr)
if(expr2 !is BinaryExpression)
return expr2
val leftDt = expr2.left.inferType(program)
val rightDt = expr2.right.inferType(program)
if(leftDt!=null && rightDt!=null && leftDt!=rightDt) {
// determine common datatype and add typecast as required to make left and right equal types
val (commonDt, toFix) = expr.commonDatatype(leftDt, rightDt, expr.left, expr.right)
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt, rightDt, expr2.left, expr2.right)
if(toFix!=null) {
when {
toFix===expr.left -> {
expr.left = TypecastExpression(expr.left, commonDt, true, expr.left.position)
expr.left.linkParents(expr)
toFix===expr2.left -> {
expr2.left = TypecastExpression(expr2.left, commonDt, true, expr2.left.position)
expr2.left.linkParents(expr2)
}
toFix===expr.right -> {
expr.right = TypecastExpression(expr.right, commonDt, true, expr.right.position)
expr.right.linkParents(expr)
toFix===expr2.right -> {
expr2.right = TypecastExpression(expr2.right, commonDt, true, expr2.right.position)
expr2.right.linkParents(expr2)
}
else -> throw FatalAstException("confused binary expression side")
}
}
}
return super.visit(expr)
return expr2
}
override fun visit(assignment: Assignment): Statement {
@ -295,7 +298,6 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
}
}
is BuiltinFunctionStatementPlaceholder -> {
// if(sub.name in setOf("lsl", "lsr", "rol", "ror", "rol2", "ror2", "memset", "memcopy", "memsetw", "swap"))
val func = BuiltinFunctions.getValue(sub.name)
if(func.pure) {
// non-pure functions don't get automatic typecasts because sometimes they act directly on their parameters
@ -317,7 +319,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
}
}
null -> {}
else -> TODO("call to something weird $sub ${call.target}")
else -> throw FatalAstException("call to something weird $sub ${call.target}")
}
}
@ -329,33 +331,13 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
return super.visit(typecast)
}
override fun visit(whenStatement: WhenStatement): Statement {
// make sure all choices are just for one single value
val choices = whenStatement.choices.toList()
for(choice in choices) {
if(choice.values==null || choice.values.size==1)
continue
for(v in choice.values) {
val newchoice=WhenChoice(listOf(v), choice.statements, choice.position)
newchoice.parent = choice.parent
whenStatement.choices.add(newchoice)
}
whenStatement.choices.remove(choice)
}
// sort the choices in low-to-high value order (nulls last)
whenStatement.choices
.sortWith(compareBy<WhenChoice, Int?>(nullsLast(), {it.values?.single()?.constValue(program)?.number?.toInt()}))
return super.visit(whenStatement)
}
override fun visit(memread: DirectMemoryRead): Expression {
// make sure the memory address is an uword
val dt = memread.addressExpression.inferType(program)
if(dt!=DataType.UWORD) {
val literaladdr = memread.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memread.addressExpression = literaladdr.cast(DataType.UWORD)!!
memread.addressExpression = literaladdr.cast(DataType.UWORD)
} else {
memread.addressExpression = TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
memread.addressExpression.parent = memread
@ -369,7 +351,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
if(dt!=DataType.UWORD) {
val literaladdr = memwrite.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)!!
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)
} else {
memwrite.addressExpression = TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
memwrite.addressExpression.parent = memwrite

View File

@ -3,13 +3,16 @@ package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.HeapValues
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
import prog8.functions.FunctionSignature
internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope, private val heap: HeapValues): IAstModifyingVisitor {
internal class VarInitValueAndAddressOfCreator(private val program: Program): IAstModifyingVisitor {
// For VarDecls that declare an initialization value:
// Replace the vardecl with an assignment (to set the initial value),
// and add a new vardecl with the default constant value of that type (usually zero) to the scope.
@ -42,7 +45,7 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
val array = ReferenceLiteralValue(decl.datatype, null,
Array(arraysize) { NumericLiteralValue.optimalInteger(0, decl.position) },
null, decl.position)
array.addToHeap(heap)
array.addToHeap(program.heap)
decl.value = array
}
@ -54,10 +57,8 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
addVarDecl(scope, decl.asDefaultValueDecl(null))
val declvalue = decl.value!!
val value =
if(declvalue is NumericLiteralValue) {
val converted = declvalue.cast(decl.datatype)
converted ?: declvalue
}
if(declvalue is NumericLiteralValue)
declvalue.cast(decl.datatype)
else
declvalue
val identifierName = listOf(decl.name) // this was: (scoped name) decl.scopedname.split(".")
@ -73,20 +74,29 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
}
override fun visit(functionCall: FunctionCall): Expression {
val targetStatement = functionCall.target.targetSubroutine(namespace)
var parentStatement: Node = functionCall
while(parentStatement !is Statement)
parentStatement = parentStatement.parent
val targetStatement = functionCall.target.targetSubroutine(program.namespace)
if(targetStatement!=null) {
var node: Node = functionCall
while(node !is Statement)
node=node.parent
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, node)
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, parentStatement)
} else {
val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")]
if(builtinFunc!=null)
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.arglist, parentStatement)
}
return functionCall
}
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
val targetStatement = functionCallStatement.target.targetSubroutine(namespace)
if(targetStatement!=null)
val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace)
if(targetStatement!=null) {
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement)
} else {
val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")]
if(builtinFunc!=null)
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.arglist, functionCallStatement)
}
return functionCallStatement
}
@ -99,10 +109,9 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
val idref = argparam.second as? IdentifierReference
val strvalue = argparam.second as? ReferenceLiteralValue
if(idref!=null) {
val variable = idref.targetVarDecl(namespace)
val variable = idref.targetVarDecl(program.namespace)
if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) {
val pointerExpr = AddressOf(idref, idref.position)
pointerExpr.scopedname = parent.makeScopedName(idref.nameInSource.single())
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
}
@ -110,12 +119,11 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
else if(strvalue!=null) {
if(strvalue.isString) {
// add a vardecl so that the autovar can be resolved in later lookups
val variable = VarDecl.createAuto(strvalue, heap)
val variable = VarDecl.createAuto(strvalue, program.heap)
addVarDecl(strvalue.definingScope(), variable)
// replace the argument with &autovar
val autoHeapvarRef = IdentifierReference(listOf(variable.name), strvalue.position)
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
pointerExpr.scopedname = parent.makeScopedName(variable.name)
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
}
@ -124,6 +132,22 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
}
}
private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FunctionSignature, args: MutableList<Expression>, parent: Statement) {
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
for(arg in args.withIndex().zip(signature.parameters)) {
val argvalue = arg.first.value
val argDt = argvalue.inferType(program)
if(argDt in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) {
if(argvalue !is IdentifierReference)
throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue")
val addrOf = AddressOf(argvalue, argvalue.position)
args[arg.first.index] = addrOf
addrOf.linkParents(parent)
}
}
}
private fun addVarDecl(scope: INameScope, variable: VarDecl) {
if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableListOf()

View File

@ -49,7 +49,7 @@ class BuiltinFunctionStatementPlaceholder(val name: String, override val positio
}
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean?)
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean)
class Block(override val name: String,
@ -192,13 +192,16 @@ class VarDecl(val type: VarDeclType,
override val expensiveToInline
get() = value!=null && value !is NumericLiteralValue
// prefix for literal values that are turned into a variable on the heap
companion object {
private var autoHeapValueSequenceNumber = 0
fun createAuto(refLv: ReferenceLiteralValue, heap: HeapValues): VarDecl {
if(refLv.heapId==null)
throw FatalAstException("can only create autovar for a ref lv that has a heapid $refLv")
val autoVarName = "$autoHeapValuePrefix${refLv.heapId}"
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
return if(refLv.isArray) {
val declaredType = ArrayElementTypes.getValue(refLv.type)
val arraysize = ArrayIndex.forArray(refLv, heap)
@ -281,6 +284,12 @@ class VarDecl(val type: VarDeclType,
structHasBeenFlattened = true
return result
}
fun withPrefixedName(nameprefix: String): Statement {
val new = VarDecl(type, declaredDatatype, zeropage, arraysize, nameprefix+name, structName, value, isArray, autogeneratedDontRemove, position)
new.parent = parent
return new
}
}
class ArrayIndex(var index: Expression, override val position: Position) : Node {
@ -301,6 +310,7 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
fun accept(visitor: IAstModifyingVisitor) {
index = index.accept(visitor)
}
fun accept(visitor: IAstVisitor) {
index.accept(visitor)
}
@ -337,9 +347,9 @@ class VariableInitializationAssignment(target: AssignTarget, aug_op: String?, va
: Assignment(target, aug_op, value, position)
data class AssignTarget(val register: Register?,
val identifier: IdentifierReference?,
val arrayindexed: ArrayIndexedExpression?,
var memoryAddress: DirectMemoryWrite?,
var identifier: IdentifierReference?,
var arrayindexed: ArrayIndexedExpression?,
val memoryAddress: DirectMemoryWrite?,
override val position: Position) : Node {
override lateinit var parent: Node
@ -370,12 +380,12 @@ data class AssignTarget(val register: Register?,
return DataType.UBYTE
if(identifier!=null) {
val symbol = program.namespace.lookup(identifier.nameInSource, stmt) ?: return null
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return null
if (symbol is VarDecl) return symbol.datatype
}
if(arrayindexed!=null) {
val dt = arrayindexed.inferType(program)
val dt = arrayindexed!!.inferType(program)
if(dt!=null)
return dt
}
@ -390,12 +400,12 @@ data class AssignTarget(val register: Register?,
return when {
this.memoryAddress!=null -> false
this.register!=null -> value is RegisterExpr && value.register==register
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier.nameInSource
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier!!.nameInSource
this.arrayindexed!=null -> value is ArrayIndexedExpression &&
value.identifier.nameInSource==arrayindexed.identifier.nameInSource &&
value.identifier.nameInSource==arrayindexed!!.identifier.nameInSource &&
value.arrayspec.size()!=null &&
arrayindexed.arrayspec.size()!=null &&
value.arrayspec.size()==arrayindexed.arrayspec.size()
arrayindexed!!.arrayspec.size()!=null &&
value.arrayspec.size()==arrayindexed!!.arrayspec.size()
else -> false
}
}
@ -406,16 +416,16 @@ data class AssignTarget(val register: Register?,
if(this.register!=null && other.register!=null)
return this.register==other.register
if(this.identifier!=null && other.identifier!=null)
return this.identifier.nameInSource==other.identifier.nameInSource
return this.identifier!!.nameInSource==other.identifier!!.nameInSource
if(this.memoryAddress!=null && other.memoryAddress!=null) {
val addr1 = this.memoryAddress!!.addressExpression.constValue(program)
val addr2 = other.memoryAddress!!.addressExpression.constValue(program)
val addr1 = this.memoryAddress.addressExpression.constValue(program)
val addr2 = other.memoryAddress.addressExpression.constValue(program)
return addr1!=null && addr2!=null && addr1==addr2
}
if(this.arrayindexed!=null && other.arrayindexed!=null) {
if(this.arrayindexed.identifier.nameInSource == other.arrayindexed.identifier.nameInSource) {
val x1 = this.arrayindexed.arrayspec.index.constValue(program)
val x2 = other.arrayindexed.arrayspec.index.constValue(program)
if(this.arrayindexed!!.identifier.nameInSource == other.arrayindexed!!.identifier.nameInSource) {
val x1 = this.arrayindexed!!.arrayspec.index.constValue(program)
val x2 = other.arrayindexed!!.arrayspec.index.constValue(program)
return x1!=null && x2!=null && x1==x2
}
}
@ -428,12 +438,12 @@ data class AssignTarget(val register: Register?,
if(this.memoryAddress!=null)
return false
if(this.arrayindexed!=null) {
val targetStmt = this.arrayindexed.identifier.targetVarDecl(namespace)
val targetStmt = this.arrayindexed!!.identifier.targetVarDecl(namespace)
if(targetStmt!=null)
return targetStmt.type!= VarDeclType.MEMORY
}
if(this.identifier!=null) {
val targetStmt = this.identifier.targetVarDecl(namespace)
val targetStmt = this.identifier!!.targetVarDecl(namespace)
if(targetStmt!=null)
return targetStmt.type!= VarDeclType.MEMORY
}
@ -518,15 +528,15 @@ class AnonymousScope(override var statements: MutableList<Statement>,
override val expensiveToInline
get() = statements.any { it.expensiveToInline }
companion object {
private var sequenceNumber = 1
}
init {
name = "<anon-$sequenceNumber>" // make sure it's an invalid soruce code identifier so user source code can never produce it
sequenceNumber++
}
companion object {
private var sequenceNumber = 1
}
override fun linkParents(parent: Node) {
this.parent = parent
statements.forEach { it.linkParents(this) }
@ -684,7 +694,7 @@ class BranchStatement(var condition: BranchCondition,
class ForLoop(val loopRegister: Register?,
val decltype: DataType?,
val zeropage: ZeropageWish,
val loopVar: IdentifierReference?,
var loopVar: IdentifierReference?,
var iterable: Expression,
var body: AnonymousScope,
override val position: Position) : Statement() {
@ -704,10 +714,6 @@ class ForLoop(val loopRegister: Register?,
override fun toString(): String {
return "ForLoop(loopVar: $loopVar, loopReg: $loopRegister, iterable: $iterable, pos=$position)"
}
companion object {
const val iteratorLoopcounterVarname = "prog8forloopcounter"
}
}
class WhileLoop(var condition: Expression,
@ -742,8 +748,8 @@ class RepeatLoop(var body: AnonymousScope,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
}
class WhenStatement(val condition: Expression,
val choices: MutableList<WhenChoice>,
class WhenStatement(var condition: Expression,
var choices: MutableList<WhenChoice>,
override val position: Position): Statement() {
override lateinit var parent: Node
override val expensiveToInline: Boolean = true
@ -761,7 +767,7 @@ class WhenStatement(val condition: Expression,
if(choice.values==null)
result.add(null to choice)
else {
val values = choice.values.map { it.constValue(program)?.number?.toInt() }
val values = choice.values!!.map { it.constValue(program)?.number?.toInt() }
if(values.contains(null))
result.add(null to choice)
else
@ -775,8 +781,8 @@ class WhenStatement(val condition: Expression,
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
}
class WhenChoice(val values: List<Expression>?, // if null, this is the 'else' part
val statements: AnonymousScope,
class WhenChoice(var values: List<Expression>?, // if null, this is the 'else' part
var statements: AnonymousScope,
override val position: Position) : Node {
override lateinit var parent: Node
@ -812,6 +818,8 @@ class StructDecl(override val name: String,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
fun nameOfFirstMember() = (statements.first() as VarDecl).name
}
class DirectMemoryWrite(var addressExpression: Expression, override val position: Position) : Node {

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,11 @@
package prog8.compiler
import prog8.ast.AstToSourceCode
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.statements.Directive
import prog8.compiler.target.c64.AsmGen
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.codegen2.AsmGen2
import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements
import prog8.optimizer.simplifyExpressions
@ -12,19 +13,25 @@ import prog8.parser.ParsingFailedError
import prog8.parser.importLibraryModule
import prog8.parser.importModule
import prog8.parser.moduleName
import java.io.File
import java.io.PrintStream
import java.nio.file.Path
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
class CompilationResult(val success: Boolean,
val programAst: Program,
val programName: String,
val importedFiles: List<Path>)
fun compileProgram(filepath: Path,
optimize: Boolean, optimizeInlining: Boolean,
generateVmCode: Boolean, writeVmCode: Boolean,
writeAssembly: Boolean): Pair<Program, String?> {
optimize: Boolean,
writeAssembly: Boolean): CompilationResult {
lateinit var programAst: Program
var programName: String? = null
var importedFiles: List<Path> = emptyList()
var success=false
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
@ -32,6 +39,8 @@ fun compileProgram(filepath: Path,
programAst = Program(moduleName(filepath.fileName), mutableListOf())
importModule(programAst, filepath)
importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map{ it.source }
val compilerOptions = determineCompilationOptions(programAst)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
@ -60,11 +69,6 @@ fun compileProgram(filepath: Path,
//println(" time2: $time2")
val time3 = measureTimeMillis {
programAst.removeNopsFlattenAnonScopes()
// if you want to print the AST, do it before shuffling the statements around below
//printAst(programAst)
programAst.reorderStatements() // reorder statements and add type casts, to please the compiler later
}
//println(" time3: $time3")
@ -80,7 +84,7 @@ fun compileProgram(filepath: Path,
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.optimizeStatements(optimizeInlining)
val optsDone2 = programAst.optimizeStatements()
if (optsDone1 + optsDone2 == 0)
break
}
@ -90,29 +94,17 @@ fun compileProgram(filepath: Path,
programAst.checkValid(compilerOptions) // check if final tree is valid
programAst.checkRecursion() // check if there are recursive subroutine calls
if(generateVmCode) {
// compile the syntax tree into stackvmProg form, and optimize that
val compiler = Compiler(programAst)
val intermediate = compiler.compile(compilerOptions)
if (optimize)
intermediate.optimize()
// printAst(programAst)
if (writeVmCode) {
val stackVmFilename = intermediate.name + ".vm.txt"
val stackvmFile = PrintStream(File(stackVmFilename), "utf-8")
intermediate.writeCode(stackvmFile)
stackvmFile.close()
println("StackVM program code written to '$stackVmFilename'")
}
if (writeAssembly) {
val zeropage = MachineDefinition.C64Zeropage(compilerOptions)
intermediate.allocateZeropage(zeropage)
val assembly = AsmGen(compilerOptions, intermediate, programAst.heap, zeropage).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
programName = assembly.name
}
if(writeAssembly) {
// asm generation directly from the Ast, no need for intermediate code
val zeropage = MachineDefinition.C64Zeropage(compilerOptions)
programAst.anonscopeVarsCleanup()
val assembly = AsmGen2(programAst, compilerOptions, zeropage).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
programName = assembly.name
}
success = true
}
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
@ -120,12 +112,10 @@ fun compileProgram(filepath: Path,
System.err.print("\u001b[91m") // bright red
System.err.println(px.message)
System.err.print("\u001b[0m") // reset
exitProcess(1)
} catch (ax: AstException) {
System.err.print("\u001b[91m") // bright red
System.err.println(ax.toString())
System.err.print("\u001b[0m") // reset
exitProcess(1)
} catch (x: Exception) {
print("\u001b[91m") // bright red
println("\n* internal error *")
@ -139,7 +129,7 @@ fun compileProgram(filepath: Path,
System.out.flush()
throw x
}
return Pair(programAst, programName)
return CompilationResult(success, programAst, programName ?: "", importedFiles)
}
fun printAst(programAst: Program) {

View File

@ -18,6 +18,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
fun allocate(scopedname: String, datatype: DataType, position: Position?): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}
if(options.zeropage==ZeropageType.DONTUSE)
throw CompilerException("zero page usage has been disabled")
val size =
when (datatype) {
in ByteDatatypes -> 1

File diff suppressed because it is too large Load Diff

View File

@ -9,10 +9,25 @@ class AssemblyProgram(val name: String) {
private val assemblyFile = "$name.asm"
private val viceMonListFile = "$name.vice-mon-list"
companion object {
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
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")
}
fun assemble(options: CompilationOptions) {
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch", "-Wall", "-Wno-strict-bool",
"-Werror", "-Wno-error=long-branch", "--dump-labels", "--vice-labels", "-l", viceMonListFile, "--no-monitor")
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch",
"--dump-labels", "--vice-labels", "-l", viceMonListFile, "--no-monitor")
val outFile = when(options.output) {
OutputType.PRG -> {
@ -41,7 +56,7 @@ class AssemblyProgram(val name: String) {
private fun generateBreakpointList() {
// builds list of breakpoints, appends to monitor list file
val breakpoints = mutableListOf<String>()
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that's generated for them
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that"s generated for them
for(line in File(viceMonListFile).readLines()) {
val match = pattern.matchEntire(line)
if(match!=null)

View File

@ -42,14 +42,14 @@ object MachineDefinition {
}
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
ZeropageType.BASICSAFE -> ExitProgramStrategy.CLEAN_EXIT
ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
}
init {
if (options.floats && options.zeropage != ZeropageType.FLOATSAFE && options.zeropage != ZeropageType.BASICSAFE)
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe'")
if (options.floats && options.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
if (options.zeropage == ZeropageType.FULL) {
free.addAll(0x04..0xf9)
@ -84,11 +84,16 @@ object MachineDefinition {
))
}
// add the other free Zp addresses,
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
free.addAll(listOf(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0d, 0x0e,
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
if(options.zeropage!=ZeropageType.DONTUSE) {
// add the other free Zp addresses,
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
} else {
// don't use the zeropage at all
free.clear()
}
}
assert(SCRATCH_B1 !in free)
assert(SCRATCH_REG !in free)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
package prog8.compiler.target.c64.codegen2
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.NameError
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.Statement
import prog8.ast.statements.VarDecl
class AnonymousScopeVarsCleanup(val program: Program): IAstModifyingVisitor {
private val checkResult: MutableList<AstException> = mutableListOf()
private val varsToMove: MutableMap<AnonymousScope, List<VarDecl>> = mutableMapOf()
fun result(): List<AstException> {
return checkResult
}
override fun visit(program: Program) {
varsToMove.clear()
super.visit(program)
for((scope, decls) in varsToMove) {
val sub = scope.definingSubroutine()!!
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associate { it.name to it }
var conflicts = false
decls.forEach {
val existing = existingVariables[it.name]
if (existing!=null) {
checkResult.add(NameError("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position))
conflicts = true
}
}
if (!conflicts) {
decls.forEach { scope.remove(it) }
sub.statements.addAll(0, decls)
decls.forEach { it.parent = sub }
}
}
}
override fun visit(scope: AnonymousScope): Statement {
val scope2 = super.visit(scope) as AnonymousScope
val vardecls = scope2.statements.filterIsInstance<VarDecl>()
varsToMove[scope2] = vardecls
return scope2
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
package prog8.compiler.target.c64
package prog8.compiler.target.c64.codegen2
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
@ -54,7 +54,7 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
numberOfOptimizations++
}
// TODO more assembly optimizations?
// TODO more assembly optimizations
return numberOfOptimizations
}
@ -89,7 +89,6 @@ fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>
lines[1].value.trim()=="dex" &&
lines[2].value.trim()=="inx" &&
lines[3].value.trim()=="lda $ESTACK_LO_HEX,x") {
removeLines.add(lines[0].index)
removeLines.add(lines[1].index)
removeLines.add(lines[2].index)
removeLines.add(lines[3].index)
@ -102,7 +101,7 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
// the float one is the one that requires 2*7=14 lines of code to check...
// @todo a better place to do this is in the Compiler instead and work on opcodes, and never even create the inefficient asm...
// @todo a better place to do this is in the Compiler instead and transform the Ast, and never even create the inefficient asm in the first place...
val removeLines = mutableListOf<Int>()
for (pair in linesByFourteen) {

View File

@ -0,0 +1,305 @@
package prog8.compiler.target.c64.codegen2
import prog8.ast.IFunctionCall
import prog8.ast.Program
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.Register
import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.FunctionCallStatement
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.toHex
import prog8.functions.FunctionSignature
internal class BuiltinFunctionsAsmGen(private val program: Program,
private val options: CompilationOptions,
private val zeropage: Zeropage,
private val asmgen: AsmGen2) {
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FunctionSignature) {
translateFunctioncall(fcall, func, false)
}
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FunctionSignature) {
translateFunctioncall(fcall, func, true)
}
private fun translateFunctioncall(fcall: IFunctionCall, func: FunctionSignature, discardResult: Boolean) {
val functionName = fcall.target.nameInSource.last()
if(discardResult) {
if(func.pure)
return // can just ignore the whole function call altogether
else if(func.returntype!=null)
throw AssemblyError("discarding result of non-pure function $fcall")
}
when(functionName) {
"msb" -> {
val arg = fcall.arglist.single()
if(arg.inferType(program) !in WordDatatypes)
throw AssemblyError("msb required word argument")
if(arg is NumericLiteralValue)
throw AssemblyError("should have been const-folded")
if(arg is IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(arg)
asmgen.out(" lda $sourceName+1 | sta $ESTACK_LO_HEX,x | dex")
} else {
asmgen.translateExpression(arg)
asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x")
}
}
"mkword" -> {
translateFunctionArguments(fcall.arglist, func)
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x")
}
"abs" -> {
translateFunctionArguments(fcall.arglist, func)
val dt = fcall.arglist.single().inferType(program)!!
when (dt) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w")
DataType.FLOAT -> asmgen.out(" jsr c64flt.abs_f")
else -> throw AssemblyError("weird type")
}
}
"swap" -> {
val first = fcall.arglist[0]
val second = fcall.arglist[1]
asmgen.translateExpression(first)
asmgen.translateExpression(second)
// pop in reverse order
val firstTarget = AssignTarget.fromExpr(first)
val secondTarget = AssignTarget.fromExpr(second)
asmgen.assignFromEvalResult(firstTarget)
asmgen.assignFromEvalResult(secondTarget)
}
"strlen" -> {
outputPushAddressOfIdentifier(fcall.arglist[0])
asmgen.out(" jsr prog8_lib.func_strlen")
}
"min", "max", "sum" -> {
outputPushAddressAndLenghtOfArray(fcall.arglist[0])
val dt = fcall.arglist.single().inferType(program)!!
when(dt) {
DataType.ARRAY_UB, DataType.STR_S, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_ub")
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw")
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
else -> throw AssemblyError("weird type $dt")
}
}
"any", "all" -> {
outputPushAddressAndLenghtOfArray(fcall.arglist[0])
val dt = fcall.arglist.single().inferType(program)!!
when(dt) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR_S, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
else -> throw AssemblyError("weird type $dt")
}
}
"sin", "cos", "tan", "atan",
"ln", "log2", "sqrt", "rad",
"deg", "round", "floor", "ceil",
"rdnf" -> {
translateFunctionArguments(fcall.arglist, func)
asmgen.out(" jsr c64flt.func_$functionName")
}
/*
TODO this was the old code for bit rotations:
Opcode.SHL_BYTE -> AsmFragment(" asl $variable+$index", 8)
Opcode.SHR_UBYTE -> AsmFragment(" lsr $variable+$index", 8)
Opcode.SHR_SBYTE -> AsmFragment(" lda $variable+$index | asl a | ror $variable+$index")
Opcode.SHL_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
Opcode.SHR_UWORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.SHR_SWORD -> AsmFragment(" lda $variable+${index * 2 + 1} | asl a | ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.ROL_BYTE -> AsmFragment(" rol $variable+$index", 8)
Opcode.ROR_BYTE -> AsmFragment(" ror $variable+$index", 8)
Opcode.ROL_WORD -> AsmFragment(" rol $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
Opcode.ROR_WORD -> AsmFragment(" ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.ROL2_BYTE -> AsmFragment(" lda $variable+$index | cmp #\$80 | rol $variable+$index", 8)
Opcode.ROR2_BYTE -> AsmFragment(" lda $variable+$index | lsr a | bcc + | ora #\$80 |+ | sta $variable+$index", 10)
Opcode.ROL2_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2} | bcc + | inc $variable+${index * 2 + 1} |+", 20)
Opcode.ROR2_WORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2} | bcc + | lda $variable+${index * 2 + 1} | ora #\$80 | sta $variable+${index * 2 + 1} |+", 30)
*/
"lsl" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)!!
when(dt) {
in ByteDatatypes -> {
when(what) {
is RegisterExpr -> {
when(what.register) {
Register.A -> asmgen.out(" asl a")
Register.X -> asmgen.out(" txa | asl a | tax")
Register.Y -> asmgen.out(" tya | asl a | tay")
}
}
is IdentifierReference -> asmgen.out(" asl ${asmgen.asmIdentifierName(what)}")
is DirectMemoryRead -> {
if(what.addressExpression is NumericLiteralValue) {
asmgen.out(" asl ${(what.addressExpression as NumericLiteralValue).number.toHex()}")
} else {
TODO("lsl memory byte $what")
}
}
is ArrayIndexedExpression -> {
TODO("lsl byte array $what")
}
else -> throw AssemblyError("weird type")
}
}
in WordDatatypes -> {
TODO("lsl word $what")
}
else -> throw AssemblyError("weird type")
}
}
"lsr" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)!!
when(dt) {
DataType.UBYTE -> {
when(what) {
is RegisterExpr -> {
when(what.register) {
Register.A -> asmgen.out(" lsr a")
Register.X -> asmgen.out(" txa | lsr a | tax")
Register.Y -> asmgen.out(" tya | lsr a | tay")
}
}
is IdentifierReference -> asmgen.out(" lsr ${asmgen.asmIdentifierName(what)}")
is DirectMemoryRead -> {
if(what.addressExpression is NumericLiteralValue) {
asmgen.out(" lsr ${(what.addressExpression as NumericLiteralValue).number.toHex()}")
} else {
TODO("lsr memory byte $what")
}
}
is ArrayIndexedExpression -> {
TODO("lsr byte array $what")
}
else -> throw AssemblyError("weird type")
}
}
DataType.BYTE -> {
TODO("lsr sbyte $what")
}
DataType.UWORD -> {
TODO("lsr sword $what")
}
DataType.WORD -> {
TODO("lsr word $what")
}
else -> throw AssemblyError("weird type")
}
}
"rol" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)!!
when(dt) {
DataType.UBYTE -> {
TODO("rol ubyte")
}
DataType.UWORD -> {
TODO("rol uword")
}
else -> throw AssemblyError("weird type")
}
}
"rol2" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)!!
when(dt) {
DataType.UBYTE -> {
TODO("rol2 ubyte")
}
DataType.UWORD -> {
TODO("rol2 uword")
}
else -> throw AssemblyError("weird type")
}
}
"ror" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)!!
when(dt) {
DataType.UBYTE -> {
TODO("ror ubyte")
}
DataType.UWORD -> {
TODO("ror uword")
}
else -> throw AssemblyError("weird type")
}
}
"ror2" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)!!
when(dt) {
DataType.UBYTE -> {
TODO("ror2 ubyte")
}
DataType.UWORD -> {
TODO("ror2 uword")
}
else -> throw AssemblyError("weird type")
}
}
else -> {
translateFunctionArguments(fcall.arglist, func)
asmgen.out(" jsr prog8_lib.func_$functionName")
}
}
}
private fun outputPushAddressAndLenghtOfArray(arg: Expression) {
arg as IdentifierReference
val identifierName = asmgen.asmIdentifierName(arg)
val size = arg.targetVarDecl(program.namespace)!!.arraysize!!.size()!!
asmgen.out("""
lda #<$identifierName
sta $ESTACK_LO_HEX,x
lda #>$identifierName
sta $ESTACK_HI_HEX,x
dex
lda #$size
sta $ESTACK_LO_HEX,x
dex
""")
}
private fun outputPushAddressOfIdentifier(arg: Expression) {
val identifierName = asmgen.asmIdentifierName(arg as IdentifierReference)
asmgen.out("""
lda #<$identifierName
sta $ESTACK_LO_HEX,x
lda #>$identifierName
sta $ESTACK_HI_HEX,x
dex
""")
}
private fun translateFunctionArguments(args: MutableList<Expression>, signature: FunctionSignature) {
args.forEach {
asmgen.translateExpression(it)
}
}
}

View File

@ -52,7 +52,6 @@ val BuiltinFunctions = mapOf(
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
"avg" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.FLOAT) { a, p, _ -> collectionArgNeverConst(a, p) },
"round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
@ -145,12 +144,11 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
return when (function) {
"abs" -> {
when(val dt = args.single().inferType(program)) {
in ByteDatatypes -> DataType.UBYTE
in WordDatatypes -> DataType.UWORD
DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("weird datatype passed to abs $dt")
}
val dt = args.single().inferType(program)
if(dt in NumericDatatypes)
return dt
else
throw FatalAstException("weird datatype passed to abs $dt")
}
"max", "min" -> {
when(val dt = datatypeFromIterableArg(args.single())) {

View File

@ -111,7 +111,8 @@ class CallGraph(private val program: Program): IAstVisitor {
override fun visit(subroutine: Subroutine) {
val alwaysKeepSubroutines = setOf(
Pair("main", "start"),
Pair("irq", "irq")
Pair("irq", "irq"),
Pair("prog8_lib", "init_system")
)
if(Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines

View File

@ -5,11 +5,12 @@ import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.fixupArrayDatatype
import prog8.ast.statements.*
import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.codegen2.AssemblyError
import prog8.functions.BuiltinFunctions
import kotlin.math.floor
@ -35,13 +36,9 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
}
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
val refLv = decl.value as? ReferenceLiteralValue
if(refLv!=null && refLv.isArray && refLv.heapId!=null)
fixupArrayTypeOnHeap(decl, refLv)
if(decl.isArray){
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
if(decl.arraysize==null) {
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
val arrayval = (decl.value as? ReferenceLiteralValue)?.array
if(arrayval!=null) {
decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.size, decl.position), decl.position)
@ -78,7 +75,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
val numericLv = decl.value as? NumericLiteralValue
val rangeExpr = decl.value as? RangeExpr
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array (will be put on heap later)
// convert the initializer range expression to an actual array
val declArraySize = decl.arraysize?.size()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.add(ExpressionError("range expression size doesn't match declared array size", decl.value?.position!!))
@ -124,8 +121,12 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
}
else -> {}
}
val heapId = program.heap.addIntegerArray(decl.datatype, Array(size) { IntegerOrAddressOf(fillvalue, null) })
decl.value = ReferenceLiteralValue(decl.datatype, initHeapId = heapId, position = numericLv.position)
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue.optimalInteger(it, numericLv.position) as Expression}.toTypedArray()
val refValue = ReferenceLiteralValue(decl.datatype, array = array, position = numericLv.position)
refValue.addToHeap(program.heap)
decl.value = refValue
refValue.parent=decl
optimizationsDone++
return super.visit(decl)
}
@ -142,8 +143,12 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
if (fillvalue < FLOAT_MAX_NEGATIVE || fillvalue > FLOAT_MAX_POSITIVE)
errors.add(ExpressionError("float value overflow", litval.position))
else {
val heapId = program.heap.addDoublesArray(DoubleArray(size) { fillvalue })
decl.value = ReferenceLiteralValue(DataType.ARRAY_F, initHeapId = heapId, position = litval.position)
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray()
val refValue = ReferenceLiteralValue(DataType.ARRAY_F, array = array, position = litval.position)
refValue.addToHeap(program.heap)
decl.value = refValue
refValue.parent=decl
optimizationsDone++
return super.visit(decl)
}
@ -158,36 +163,6 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
return super.visit(decl)
}
private fun fixupArrayTypeOnHeap(decl: VarDecl, litval: ReferenceLiteralValue) {
// fix the type of the array value that's on the heap, to match the vardecl.
// notice that checking the bounds of the actual values is not done here, but in the AstChecker later.
if(decl.datatype==litval.type)
return // already correct datatype
val heapId = litval.heapId ?: throw FatalAstException("expected array to be on heap $litval")
val array = program.heap.get(heapId)
when(decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
if(array.array!=null) {
program.heap.update(heapId, HeapValues.HeapValue(decl.datatype, null, array.array, null))
decl.value = ReferenceLiteralValue(decl.datatype, initHeapId = heapId, position = litval.position)
}
}
DataType.ARRAY_F -> {
if(array.array!=null) {
// convert a non-float array to floats
val doubleArray = array.array.map { it.integer!!.toDouble() }.toDoubleArray()
program.heap.update(heapId, HeapValues.HeapValue(DataType.ARRAY_F, null, null, doubleArray))
decl.value = ReferenceLiteralValue(decl.datatype, initHeapId = heapId, position = litval.position)
}
}
DataType.STRUCT -> {
// leave it alone for structs.
}
else -> throw FatalAstException("invalid array vardecl type ${decl.datatype}")
}
}
/**
* replace identifiers that refer to const value, with the value itself (if it's a simple type)
*/
@ -200,7 +175,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
copy.parent = identifier.parent
copy
}
cval.type in PassByReferenceDatatypes -> TODO("ref type $identifier")
cval.type in PassByReferenceDatatypes -> throw AssemblyError("pass-by-reference type should not be considered a constant")
else -> identifier
}
} catch (ax: AstException) {
@ -227,6 +202,23 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
}
private fun typeCastConstArguments(functionCall: IFunctionCall) {
if(functionCall.target.nameInSource.size==1) {
val builtinFunction = BuiltinFunctions[functionCall.target.nameInSource.single()]
if(builtinFunction!=null) {
// match the arguments of a builtin function signature.
for(arg in functionCall.arglist.withIndex().zip(builtinFunction.parameters)) {
val possibleDts = arg.second.possibleDatatypes
val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type !in possibleDts) {
val convertedValue = argConst.cast(possibleDts.first()) // TODO can throw exception
functionCall.arglist[arg.first.index] = convertedValue
optimizationsDone++
}
}
return
}
}
// match the arguments of a subroutine.
val subroutine = functionCall.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
// if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter
@ -234,11 +226,9 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
val expectedDt = arg.second.type
val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type!=expectedDt) {
val convertedValue = argConst.cast(expectedDt)
if(convertedValue!=null) {
functionCall.arglist[arg.first.index] = convertedValue
optimizationsDone++
}
val convertedValue = argConst.cast(expectedDt) // TODO can throw exception
functionCall.arglist[arg.first.index] = convertedValue
optimizationsDone++
}
}
}
@ -259,14 +249,16 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
*/
override fun visit(expr: PrefixExpression): Expression {
return try {
super.visit(expr)
val prefixExpr=super.visit(expr)
if(prefixExpr !is PrefixExpression)
return prefixExpr
val subexpr = expr.expression
val subexpr = prefixExpr.expression
if (subexpr is NumericLiteralValue) {
// accept prefixed literal values (such as -3, not true)
return when {
expr.operator == "+" -> subexpr
expr.operator == "-" -> when {
prefixExpr.operator == "+" -> subexpr
prefixExpr.operator == "-" -> when {
subexpr.type in IntegerDatatypes -> {
optimizationsDone++
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position)
@ -277,21 +269,21 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
}
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
}
expr.operator == "~" -> when {
prefixExpr.operator == "~" -> when {
subexpr.type in IntegerDatatypes -> {
optimizationsDone++
NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position)
}
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
}
expr.operator == "not" -> {
prefixExpr.operator == "not" -> {
optimizationsDone++
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position)
}
else -> throw ExpressionError(expr.operator, subexpr.position)
else -> throw ExpressionError(prefixExpr.operator, subexpr.position)
}
}
return expr
return prefixExpr
} catch (ax: AstException) {
addError(ax)
expr
@ -320,7 +312,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
super.visit(expr)
if(expr.left is ReferenceLiteralValue || expr.right is ReferenceLiteralValue)
TODO("binexpr with reference litval")
throw FatalAstException("binexpr with reference litval instead of numeric")
val leftconst = expr.left.constValue(program)
val rightconst = expr.right.constValue(program)
@ -552,14 +544,12 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
override fun visit(forLoop: ForLoop): Statement {
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
// TODO casts can throw exception
val newFrom = rangeFrom.cast(targetDt)
val newTo = rangeTo.cast(targetDt)
if (newFrom != null && newTo != null) {
val newStep: Expression =
if (stepLiteral != null) (stepLiteral.cast(targetDt) ?: stepLiteral) else range.step
return RangeExpr(newFrom, newTo, newStep, range.position)
}
return range
val newStep: Expression =
stepLiteral?.cast(targetDt) ?: range.step
return RangeExpr(newFrom, newTo, newStep, range.position)
}
// adjust the datatype of a range expression in for loops to the loop variable.
@ -606,68 +596,16 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
override fun visit(refLiteral: ReferenceLiteralValue): Expression {
val litval = super.visit(refLiteral)
if(litval is ReferenceLiteralValue) {
if (litval.isString) {
// intern the string; move it into the heap
if (litval.str!!.length !in 1..255)
addError(ExpressionError("string literal length must be between 1 and 255", litval.position))
else {
litval.addToHeap(program.heap) // TODO: we don't know the actual string type yet, STR != STR_S etc...
if (litval.isArray) {
val vardecl = litval.parent as? VarDecl
if (vardecl!=null) {
return fixupArrayDatatype(litval, vardecl, program.heap)
}
} else if (litval.isArray) {
// first, adjust the array datatype
val litval2 = adjustArrayValDatatype(litval)
litval2.addToHeap(program.heap)
return litval2
}
}
return litval
}
private fun adjustArrayValDatatype(litval: ReferenceLiteralValue): ReferenceLiteralValue {
if(litval.array==null) {
if(litval.heapId!=null)
return litval // thing is already on the heap, assume it's the right type
throw FatalAstException("missing array value")
}
val typesInArray = litval.array.mapNotNull { it.inferType(program) }.toSet()
val arrayDt =
when {
litval.array.any { it is AddressOf } -> DataType.ARRAY_UW
DataType.FLOAT in typesInArray -> DataType.ARRAY_F
DataType.WORD in typesInArray -> DataType.ARRAY_W
else -> {
val allElementsAreConstantOrAddressOf = litval.array.fold(true) { c, expr-> c and (expr is NumericLiteralValue|| expr is AddressOf)}
if(!allElementsAreConstantOrAddressOf) {
addError(ExpressionError("array literal can only consist of constant primitive numerical values or memory pointers", litval.position))
return litval
} else {
val integerArray = litval.array.map { it.constValue(program)!!.number.toInt() }
val maxValue = integerArray.max()!!
val minValue = integerArray.min()!!
if (minValue >= 0) {
// unsigned
if (maxValue <= 255)
DataType.ARRAY_UB
else
DataType.ARRAY_UW
} else {
// signed
if (maxValue <= 127)
DataType.ARRAY_B
else
DataType.ARRAY_W
}
}
}
}
if(arrayDt!=litval.type) {
return ReferenceLiteralValue(arrayDt, array = litval.array, position = litval.position)
}
return litval
}
override fun visit(assignment: Assignment): Statement {
super.visit(assignment)
val lv = assignment.value as? NumericLiteralValue

View File

@ -27,8 +27,8 @@ internal fun Program.constantFold() {
}
internal fun Program.optimizeStatements(optimizeInlining: Boolean): Int {
val optimizer = StatementOptimizer(this, optimizeInlining)
internal fun Program.optimizeStatements(): Int {
val optimizer = StatementOptimizer(this)
optimizer.visit(this)
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration

View File

@ -43,7 +43,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val literal = tc.expression as? NumericLiteralValue
if(literal!=null) {
val newLiteral = literal.cast(tc.type)
if(newLiteral!=null && newLiteral!==literal) {
if(newLiteral!==literal) {
optimizationsDone++
return newLiteral
}

View File

@ -14,89 +14,23 @@ import kotlin.math.floor
/*
todo: subroutines with 1 or 2 byte args or 1 word arg can be converted to asm sub calling convention (args in registers)
todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to) + print warning about this
TODO: proper inlining of small subroutines (correctly renaming/relocating all variables in them and refs to those as well, or restrict to subs without variables?)
*/
internal class StatementOptimizer(private val program: Program, private val optimizeInlining: Boolean) : IAstModifyingVisitor {
internal class StatementOptimizer(private val program: Program) : IAstModifyingVisitor {
var optimizationsDone: Int = 0
private set
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
private val callgraph = CallGraph(program)
companion object {
private var generatedLabelSequenceNumber = 0
}
override fun visit(program: Program) {
removeUnusedCode(callgraph)
if(optimizeInlining) {
inlineSubroutines(callgraph)
}
super.visit(program)
}
private fun inlineSubroutines(callgraph: CallGraph) {
val entrypoint = program.entrypoint()
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
if(sub!==entrypoint && !sub.isAsmSubroutine) {
if (sub.statements.size <= 3 && !sub.expensiveToInline) {
sub.calledBy.toList().forEach { caller -> inlineSubroutine(sub, caller) }
} else if (sub.calledBy.size==1 && sub.statements.size < 50) {
inlineSubroutine(sub, sub.calledBy[0])
} else if(sub.calledBy.size<=3 && sub.statements.size < 10 && !sub.expensiveToInline) {
sub.calledBy.toList().forEach { caller -> inlineSubroutine(sub, caller) }
}
}
}
}
}
private fun inlineSubroutine(sub: Subroutine, caller: Node) {
// if the sub is called multiple times from the isSameAs scope, we can't inline (would result in duplicate definitions)
// (unless we add a sequence number to all vars/labels and references to them in the inlined code, but I skip that for now)
val scope = caller.definingScope()
if(sub.calledBy.count { it.definingScope()===scope } > 1)
return
if(caller !is IFunctionCall || caller !is Statement || sub.statements.any { it is Subroutine })
return
if(sub.parameters.isEmpty() && sub.returntypes.isEmpty()) {
// sub without params and without return value can be easily inlined
val parent = caller.parent as INameScope
val inlined = AnonymousScope(sub.statements.toMutableList(), caller.position)
parent.statements[parent.statements.indexOf(caller)] = inlined
// replace return statements in the inlined sub by a jump to the end of it
var haveNewEndLabel = false
var endLabelUsed = false
var endlabel = inlined.statements.last() as? Label
if(endlabel==null) {
endlabel = makeLabel("_prog8_auto_sub_end", inlined.statements.last().position)
endlabel.parent = inlined
haveNewEndLabel = true
}
val returns = inlined.statements.withIndex().filter { iv -> iv.value is Return }.map { iv -> Pair(iv.index, iv.value as Return)}
for(returnIdx in returns) {
val jump = Jump(null, IdentifierReference(listOf(endlabel.name), returnIdx.second.position), null, returnIdx.second.position)
inlined.statements[returnIdx.first] = jump
endLabelUsed = true
}
if(endLabelUsed && haveNewEndLabel)
inlined.statements.add(endlabel)
inlined.linkParents(caller.parent)
sub.calledBy.remove(caller) // if there are no callers left, the sub will be removed automatically later
optimizationsDone++
} else {
// TODO inline subroutine that has params or returnvalues or both
}
}
private fun makeLabel(name: String, position: Position): Label {
generatedLabelSequenceNumber++
return Label("${name}_$generatedLabelSequenceNumber", position)
}
private fun removeUnusedCode(callgraph: CallGraph) {
// remove all subroutines that aren't called, or are empty
val removeSubroutines = mutableSetOf<Subroutine>()
@ -173,13 +107,6 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(subroutine.canBeAsmSubroutine) {
optimizationsDone++
return subroutine.intoAsmSubroutine() // TODO this doesn't work yet due to parameter vardecl issue
// TODO fix parameter passing so this also works:
// asmsub aa(byte arg @ Y) -> clobbers() -> () {
// byte local = arg ; @todo fix 'undefined symbol arg' by some sort of alias name for the parameter
// A=44
// }
}
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
@ -241,8 +168,6 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") ||
functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) {
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
if(functionCallStatement.arglist.single() is NumericLiteralValue)
throw AstException("string argument should be on heap already")
val stringVar = functionCallStatement.arglist.single() as? IdentifierReference
if(stringVar!=null) {
val heapId = stringVar.heapId(program.namespace)
@ -379,10 +304,10 @@ internal class StatementOptimizer(private val program: Program, private val opti
printWarning("condition is always true", whileLoop.position)
if(hasContinueOrBreak(whileLoop.body))
return whileLoop
val label = Label("__back", whileLoop.condition.position)
val label = Label("_prog8_back", whileLoop.condition.position)
whileLoop.body.statements.add(0, label)
whileLoop.body.statements.add(Jump(null,
IdentifierReference(listOf("__back"), whileLoop.condition.position),
IdentifierReference(listOf("_prog8_back"), whileLoop.condition.position),
null, whileLoop.condition.position))
optimizationsDone++
return whileLoop.body
@ -489,8 +414,10 @@ internal class StatementOptimizer(private val program: Program, private val opti
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
if(assignment.target isSameAs assignment.value) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
if(assignment.target.isNotMemory(program.namespace)) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
}
val targetDt = assignment.target.inferType(program, assignment)
val bexpr=assignment.value as? BinaryExpression

View File

@ -5,6 +5,7 @@ import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.Petscii
import java.util.*
import kotlin.math.abs
import kotlin.math.pow
@ -107,8 +108,6 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
asBoolean = floatval != 0.0
}
else -> {
if(heapId==null)
throw IllegalArgumentException("for non-numeric types, a heapId should be given")
byteval = null
wordval = null
floatval = null
@ -156,12 +155,7 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
}
}
override fun hashCode(): Int {
val bh = byteval?.hashCode() ?: 0x10001234
val wh = wordval?.hashCode() ?: 0x01002345
val fh = floatval?.hashCode() ?: 0x00103456
return bh xor wh xor fh xor heapId.hashCode() xor type.hashCode()
}
override fun hashCode(): Int = Objects.hash(byteval, wordval, floatval, type)
override fun equals(other: Any?): Boolean {
if(other==null || other !is RuntimeValue)
@ -628,7 +622,7 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
}
class RuntimeValueRange(type: DataType, val range: IntProgression): RuntimeValue(type, 0) {
class RuntimeValueRange(type: DataType, val range: IntProgression): RuntimeValue(type, array=range.toList().toTypedArray()) {
override fun iterator(): Iterator<Number> {
return range.iterator()
}

View File

@ -7,6 +7,7 @@ import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.*
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.vm.RuntimeValue
@ -472,7 +473,7 @@ class AstVm(val program: Program) {
loopvar = IdentifierReference(listOf(stmt.loopRegister.name), stmt.position)
} else {
loopvarDt = stmt.loopVar!!.inferType(program)!!
loopvar = stmt.loopVar
loopvar = stmt.loopVar!!
}
val iterator = iterable.iterator()
for (loopvalue in iterator) {
@ -518,7 +519,7 @@ class AstVm(val program: Program) {
executeAnonymousScope(choice.statements)
break
} else {
val value = choice.values.single().constValue(evalCtx.program) ?: throw VmExecutionException("can only use const values in when choices ${choice.position}")
val value = choice.values!!.single().constValue(evalCtx.program) ?: throw VmExecutionException("can only use const values in when choices ${choice.position}")
val rtval = RuntimeValue.fromLv(value)
if(condition==rtval) {
executeAnonymousScope(choice.statements)
@ -545,10 +546,12 @@ class AstVm(val program: Program) {
}
fun performAssignment(target: AssignTarget, value: RuntimeValue, contextStmt: Statement, evalCtx: EvalContext) {
val targetIdent = target.identifier
val targetArrayIndexed = target.arrayindexed
when {
target.identifier != null -> {
val decl = contextStmt.definingScope().lookup(target.identifier.nameInSource, contextStmt) as? VarDecl
?: throw VmExecutionException("can't find assignment target ${target.identifier}")
targetIdent != null -> {
val decl = contextStmt.definingScope().lookup(targetIdent.nameInSource, contextStmt) as? VarDecl
?: throw VmExecutionException("can't find assignment target $targetIdent")
if (decl.type == VarDeclType.MEMORY) {
val address = runtimeVariables.getMemoryAddress(decl.definingScope(), decl.name)
when (decl.datatype) {
@ -565,14 +568,14 @@ class AstVm(val program: Program) {
runtimeVariables.set(decl.definingScope(), decl.name, value)
}
target.memoryAddress != null -> {
val address = evaluate(target.memoryAddress!!.addressExpression, evalCtx).wordval!!
val address = evaluate(target.memoryAddress.addressExpression, evalCtx).wordval!!
evalCtx.mem.setUByte(address, value.byteval!!)
}
target.arrayindexed != null -> {
val vardecl = target.arrayindexed.identifier.targetVarDecl(program.namespace)!!
targetArrayIndexed != null -> {
val vardecl = targetArrayIndexed.identifier.targetVarDecl(program.namespace)!!
if(vardecl.type==VarDeclType.VAR) {
val array = evaluate(target.arrayindexed.identifier, evalCtx)
val index = evaluate(target.arrayindexed.arrayspec.index, evalCtx)
val array = evaluate(targetArrayIndexed.identifier, evalCtx)
val index = evaluate(targetArrayIndexed.arrayspec.index, evalCtx)
when (array.type) {
DataType.ARRAY_UB -> {
if (value.type != DataType.UBYTE)
@ -606,7 +609,7 @@ class AstVm(val program: Program) {
val indexInt = index.integerValue()
val newchr = Petscii.decodePetscii(listOf(value.numericValue().toShort()), true)
val newstr = array.str!!.replaceRange(indexInt, indexInt + 1, newchr)
val ident = contextStmt.definingScope().lookup(target.arrayindexed.identifier.nameInSource, contextStmt) as? VarDecl
val ident = contextStmt.definingScope().lookup(targetArrayIndexed.identifier.nameInSource, contextStmt) as? VarDecl
?: throw VmExecutionException("can't find assignment target ${target.identifier}")
val identScope = ident.definingScope()
program.heap.update(array.heapId!!, newstr)
@ -615,8 +618,8 @@ class AstVm(val program: Program) {
}
else {
val address = (vardecl.value as NumericLiteralValue).number.toInt()
val index = evaluate(target.arrayindexed.arrayspec.index, evalCtx).integerValue()
val elementType = target.arrayindexed.inferType(program)!!
val index = evaluate(targetArrayIndexed.arrayspec.index, evalCtx).integerValue()
val elementType = targetArrayIndexed.inferType(program)!!
when(elementType) {
DataType.UBYTE -> mem.setUByte(address+index, value.byteval!!)
DataType.BYTE -> mem.setSByte(address+index, value.byteval!!)
@ -673,23 +676,23 @@ class AstVm(val program: Program) {
dialog.canvas.printText(args[0].wordval!!.toString(), true)
}
"c64scr.print_ubhex" -> {
val prefix = if (args[0].asBoolean) "$" else ""
val number = args[1].byteval!!
val number = args[0].byteval!!
val prefix = if (args[1].asBoolean) "$" else ""
dialog.canvas.printText("$prefix${number.toString(16).padStart(2, '0')}", true)
}
"c64scr.print_uwhex" -> {
val prefix = if (args[0].asBoolean) "$" else ""
val number = args[1].wordval!!
val number = args[0].wordval!!
val prefix = if (args[1].asBoolean) "$" else ""
dialog.canvas.printText("$prefix${number.toString(16).padStart(4, '0')}", true)
}
"c64scr.print_uwbin" -> {
val prefix = if (args[0].asBoolean) "%" else ""
val number = args[1].wordval!!
val number = args[0].wordval!!
val prefix = if (args[1].asBoolean) "%" else ""
dialog.canvas.printText("$prefix${number.toString(2).padStart(16, '0')}", true)
}
"c64scr.print_ubbin" -> {
val prefix = if (args[0].asBoolean) "%" else ""
val number = args[1].byteval!!
val number = args[0].byteval!!
val prefix = if (args[1].asBoolean) "%" else ""
dialog.canvas.printText("$prefix${number.toString(2).padStart(8, '0')}", true)
}
"c64scr.clear_screenchars" -> {
@ -731,7 +734,7 @@ class AstVm(val program: Program) {
result = RuntimeValue(DataType.UBYTE, paddedStr.indexOf('\u0000'))
}
"c64flt.print_f" -> {
dialog.canvas.printText(args[0].floatval.toString(), true)
dialog.canvas.printText(args[0].floatval.toString(), false)
}
"c64.CHROUT" -> {
dialog.canvas.printPetscii(args[0].byteval!!)
@ -842,10 +845,6 @@ class AstVm(val program: Program) {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(ArrayElementTypes.getValue(args[0].type), numbers.min())
}
"avg" -> {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(DataType.FLOAT, numbers.average())
}
"sum" -> {
val sum = args.single().array!!.map { it.toDouble() }.sum()
when (args[0].type) {
@ -875,27 +874,31 @@ class AstVm(val program: Program) {
RuntimeValue(DataType.UBYTE, args[0].str!!.length)
}
"memset" -> {
val target = args[0].array!!
val heapId = args[0].wordval!!
val target = program.heap.get(heapId).array ?: throw VmExecutionException("memset target is not an array")
val amount = args[1].integerValue()
val value = args[2].integerValue()
for (i in 0 until amount) {
target[i] = value
target[i] = IntegerOrAddressOf(value, null)
}
null
}
"memsetw" -> {
val target = args[0].array!!
val heapId = args[0].wordval!!
val target = program.heap.get(heapId).array ?: throw VmExecutionException("memset target is not an array")
val amount = args[1].integerValue()
val value = args[2].integerValue()
for (i in 0 until amount step 2) {
target[i * 2] = value and 255
target[i * 2 + 1] = value ushr 8
target[i * 2] = IntegerOrAddressOf(value and 255, null)
target[i * 2 + 1] = IntegerOrAddressOf(value ushr 8, null)
}
null
}
"memcopy" -> {
val source = args[0].array!!
val dest = args[1].array!!
val sourceHeapId = args[0].wordval!!
val destHeapId = args[1].wordval!!
val source = program.heap.get(sourceHeapId).array!!
val dest = program.heap.get(destHeapId).array!!
val amount = args[2].integerValue()
for(i in 0 until amount) {
dest[i] = source[i]
@ -952,4 +955,3 @@ class AstVm(val program: Program) {
}
}

View File

@ -116,18 +116,6 @@ class TestRuntimeValue {
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.FLOAT, 9.0)))
}
@Test
fun testRequireHeap()
{
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.STR, num = 999) }
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.STR_S, num = 999) }
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.ARRAY_F, num = 999) }
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.ARRAY_W, num = 999) }
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.ARRAY_UW, num = 999) }
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.ARRAY_B, num = 999) }
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.ARRAY_UB, num = 999) }
}
@Test
fun testEqualityHeapTypes()
{

View File

@ -145,8 +145,12 @@ class TestZeropage {
assertFailsWith<CompilerException> {
zp.allocate("", DataType.FLOAT, null)
}
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
zp2.allocate("", DataType.FLOAT, null)
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true))
assertFailsWith<CompilerException> {
zp2.allocate("", DataType.FLOAT, null)
}
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
zp3.allocate("", DataType.FLOAT, null)
}
@Test
@ -165,14 +169,16 @@ class TestZeropage {
}
}
// TODO test dontuse option
@Test
fun testFreeSpaces() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
assertEquals(20, zp1.available())
assertEquals(16, zp1.available())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false))
assertEquals(95, zp2.available())
assertEquals(91, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false))
assertEquals(129, zp3.available())
assertEquals(125, zp3.available())
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
assertEquals(238, zp4.available())
}
@ -202,11 +208,10 @@ class TestZeropage {
@Test
fun testBasicsafeAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
assertEquals(20, zp.available())
assertEquals(16, zp.available())
zp.allocate("", DataType.FLOAT, null)
assertFailsWith<ZeropageDepletedError> {
// in regular zp there aren't 5 sequential bytes free after we take the first sequence
// in regular zp there aren't 5 sequential bytes free
zp.allocate("", DataType.FLOAT, null)
}
@ -256,16 +261,16 @@ class TestZeropage {
@Test
fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
assertEquals(20, zp.available())
assertEquals(0x04, zp.allocate("", DataType.FLOAT, null))
assertEquals(0x09, zp.allocate("", DataType.UBYTE, null))
assertEquals(0x0d, zp.allocate("", DataType.UWORD, null))
assertEquals(16, zp.available())
assertEquals(0x04, zp.allocate("", DataType.WORD, null))
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null))
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null))
assertEquals(0x94, zp.allocate("", DataType.UWORD, null))
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null))
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null))
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null))
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null))
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null))
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null))
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null))
assertEquals(0, zp.available())
}

View File

@ -87,6 +87,13 @@ If you use the option to let the compiler auto-start a C-64 emulator, it will do
a successful compilation. This will load your program and the symbol and breakpoint lists
(for the machine code monitor) into the emulator.
Continuous compilation mode
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Almost instant compilation times (less than a second) can be achieved when using the continuous compilation mode.
Start the compiler with the ``-watch`` argument to enable this.
It will compile your program and then instead of exiting, it waits for any changes in the module source files.
As soon as a change happens, the program gets compiled again.
Module source code files
------------------------
@ -101,6 +108,13 @@ They are embedded into the packaged release version of the compiler so you don't
where they are, but their names are still reserved.
User defined library files
^^^^^^^^^^^^^^^^^^^^^^^^^^
You can create library files yourself too that can be shared among programs.
You can tell the compiler where it should look for these files, by setting the java command line property ``prog8.libdir``
or by setting the ``PROG8_LIBDIR`` environment variable to the correct directory.
.. _debugging:
Debugging (with Vice)

View File

@ -45,7 +45,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
%import c64utils
%zeropage basicsafe
~ main {
main {
ubyte[256] sieve
ubyte candidate_prime = 2
@ -101,7 +101,7 @@ The following programs shows a use of the high level ``struct`` type::
%import c64utils
%zeropage basicsafe
~ main {
main {
struct Color {
ubyte red
@ -140,7 +140,8 @@ Design principles and features
- 'One statement per line' code style, resulting in clear readable programs.
- Modular programming and scoping via modules, code blocks, and subroutines.
- Provide high level programming constructs but stay close to the metal;
still able to directly use memory addresses, CPU registers and ROM subroutines
still able to directly use memory addresses, CPU registers and ROM subroutines,
and inline assembly to have full control when every cycle or byte matters
- Arbitrary number of subroutine parameters (constrained only by available memory)
- Complex nested expressions are possible
- Values are typed. Types supported include signed and unsigned bytes and words, arrays, strings and floats.
@ -150,7 +151,7 @@ Design principles and features
the compiled program in an emulator and provide debugging information to the emulator.
- The compiler outputs a regular 6502 assembly source code file, but doesn't assemble this itself.
The (separate) '64tass' cross-assembler tool is used for that.
- Goto is usually considered harmful, but not here: arbitrary control flow jumps and branches are possible,
- Arbitrary control flow jumps and branches are possible,
and will usually translate directly into the appropriate single 6502 jump/branch instruction.
- There are no complicated built-in error handling or overflow checks, you'll have to take care
of this yourself if required. This keeps the language and code simple and efficient.

View File

@ -93,7 +93,7 @@ Blocks, Scopes, and accessing Symbols
**Blocks** are the top level separate pieces of code and data of your program. They are combined
into a single output program. No code or data can occur outside a block. Here's an example::
~ main $c000 {
main $c000 {
; this is code inside the block...
}
@ -127,12 +127,20 @@ The address must be >= ``$0200`` (because ``$00``--``$ff`` is the ZP and ``$100`
to them by their 'short' name directly. If the symbol is not found in the same scope,
the enclosing scope is searched for it, and so on, until the symbol is found.
Scopes are created using several statements:
Scopes are created using either of these two statements:
- blocks (top-level named scope)
- subroutines (nested named scopes)
- for, while, repeat loops (anonymous scope)
- if statements and branching conditionals (anonymous scope)
- subroutines (nested named scope)
.. note::
In contrast to many other programming languages, a new scope is *not* created inside
for, while and repeat statements, nor for the if statement and branching conditionals.
This is a bit restrictive because you have to think harder about what variables you
want to use inside a subroutine. But it is done precisely for this reason; memory in the
target system is very limited and it would be a waste to allocate a lot of variables.
Right now the prog8 compiler is not advanced enough to be able to 'share' or 'overlap'
variables intelligently by itself. So for now, it's something the programmer has to think about.
Program Start and Entry Point
@ -151,7 +159,7 @@ taking no parameters and having no return value.
As any subroutine, it has to end with a ``return`` statement (or a ``goto`` call)::
~ main {
main {
sub start () {
; program entrypoint code here
return
@ -400,7 +408,9 @@ You can also create loops by using the ``goto`` statement, but this should usual
The value of the loop variable or register after executing the loop *is undefined*. Don't use it immediately
after the loop without first assigning a new value to it!
(this is an optimization issue to avoid having to deal with mostly useless post-loop logic to adjust the loop variable's value)
Loop variables that are declared inline are scoped in the loop body so they're not accessible at all after the loop finishes.
Loop variables that are declared inline are not different to them being
defined in a separate var declaration in the subroutine, it's just a readability convenience.
(this may change in the future if the compiler gets more advanced with additional sub-scopes)
Conditional Execution
@ -697,9 +707,6 @@ max(x)
min(x)
Minimum of the values in the array value x
avg(x)
Average of the values in the array value x
sum(x)
Sum of the values in the array value x

View File

@ -74,6 +74,7 @@ Directives
As with ``kernalsafe``, it is not possible to cleanly exit the program, other than to reset the machine.
This option makes programs smaller and faster because even more variables can
be stored in the ZP (which allows for more efficient assembly code).
- style ``dontuse`` -- don't use *any* location in the zeropage.
Also read :ref:`zeropage`.
@ -173,7 +174,7 @@ Code blocks
A named block of actual program code. Itefines a *scope* (also known as 'namespace') and
can contain Prog8 *code*, *directives*, *variable declarations* and *subroutines*::
~ <blockname> [<address>] {
<blockname> [<address>] {
<directives>
<variables>
<statements>
@ -185,7 +186,7 @@ The <address> is optional. If specified it must be a valid memory address such a
It's used to tell the compiler to put the block at a certain position in memory.
Also read :ref:`blocks`. Here is an example of a code block, to be loaded at ``$c000``::
~ main $c000 {
main $c000 {
; this is code inside the block...
}
@ -387,7 +388,7 @@ arithmetic: ``+`` ``-`` ``*`` ``/`` ``**`` ``%``
``+``, ``-``, ``*``, ``/`` are the familiar arithmetic operations.
``/`` is division (will result in integer division when using on integer operands, and a floating point division when at least one of the operands is a float)
``**`` is the power operator: ``3 ** 5`` is equal to 3*3*3*3*3 and is 243. (it only works on floating point variables)
``%`` is the remainder operator: ``25 % 7`` is 4. Be careful: without a space, %10 will be parsed as the binary number 2
``%`` is the remainder operator: ``25 % 7`` is 4. Be careful: without a space, %10 will be parsed as the binary number 2.
Remainder is only supported on integer operands (not floats).
bitwise arithmetic: ``&`` ``|`` ``^`` ``~`` ``<<`` ``>>``

View File

@ -168,7 +168,7 @@ These routines are::
If you activate an IRQ handler with one of these, it expects the handler to be defined
as a subroutine ``irq`` in the module ``irq`` so like this::
~ irq {
irq {
sub irq() {
; ... irq handling here ...
}

View File

@ -19,6 +19,15 @@ these should call optimized pieces of assembly code, so they run as fast as poss
For now, we have the ``memcopy``, ``memset`` and ``strlen`` builtin functions.
Fixes
^^^^^
fix asmsub parameters so this works::
asmsub aa(byte arg @ Y) -> clobbers() -> () {
byte local = arg ; @todo fix 'undefined symbol arg' that occurs here
A=44
}
More optimizations
@ -27,7 +36,6 @@ More optimizations
Add more compiler optimizations to the existing ones.
- on the language AST level
- on the StackVM intermediate code level
- on the final assembly source level
- can the parameter passing to subroutines be optimized to avoid copying?
@ -35,6 +43,7 @@ Add more compiler optimizations to the existing ones.
this requires rethinking the way parameters are represented, simply injecting vardecls to
declare local variables for them is not always correct anymore
- working subroutine inlining (taking care of vars and identifier refs to them)
Also some library routines and code patterns could perhaps be optimized further

View File

@ -0,0 +1,110 @@
%import c64lib
%import c64utils
%import c64flt
%zeropage dontuse
main {
sub start() {
ubyte[] ubarr = [100, 0, 99, 199, 22]
byte[] barr = [-100, 0, 99, -122, 22]
uword[] uwarr = [1000, 0, 222, 4444, 999]
word[] warr = [-1000, 0, 999, -4444, 222]
float[] farr = [-1000.1, 0, 999.9, -4444.4, 222.2]
str name = "irmen"
ubyte ub
byte bb
word ww
uword uw
float ff
; LEN/STRLEN
ubyte length = len(name)
if length!=5 c64scr.print("error len1\n")
length = len(uwarr)
if length!=5 c64scr.print("error len2\n")
length=strlen(name)
if length!=5 c64scr.print("error strlen1\n")
name[3] = 0
length=strlen(name)
if length!=3 c64scr.print("error strlen2\n")
; MAX
ub = max(ubarr)
if ub!=199 c64scr.print("error max1\n")
bb = max(barr)
if bb!=99 c64scr.print("error max2\n")
uw = max(uwarr)
if uw!=4444 c64scr.print("error max3\n")
ww = max(warr)
if ww!=999 c64scr.print("error max4\n")
ff = max(farr)
if ff!=999.9 c64scr.print("error max5\n")
; MIN
ub = min(ubarr)
if ub!=0 c64scr.print("error min1\n")
bb = min(barr)
if bb!=-122 c64scr.print("error min2\n")
uw = min(uwarr)
if uw!=0 c64scr.print("error min3\n")
ww = min(warr)
if ww!=-4444 c64scr.print("error min4\n")
ff = min(farr)
if ff!=-4444.4 c64scr.print("error min5\n")
; SUM
uw = sum(ubarr)
if uw!=420 c64scr.print("error sum1\n")
ww = sum(barr)
if ww!=-101 c64scr.print("error sum2\n")
uw = sum(uwarr)
if uw!=6665 c64scr.print("error sum3\n")
ww = sum(warr)
if ww!=-4223 c64scr.print("error sum4\n")
ff = sum(farr)
if ff!=-4222.4 c64scr.print("error sum5\n")
; ANY
ub = any(ubarr)
if ub==0 c64scr.print("error any1\n")
ub = any(barr)
if ub==0 c64scr.print("error any2\n")
ub = any(uwarr)
if ub==0 c64scr.print("error any3\n")
ub = any(warr)
if ub==0 c64scr.print("error any4\n")
ub = any(farr)
if ub==0 c64scr.print("error any5\n")
; ALL
ub = all(ubarr)
if ub==1 c64scr.print("error all1\n")
ub = all(barr)
if ub==1 c64scr.print("error all2\n")
ub = all(uwarr)
if ub==1 c64scr.print("error all3\n")
ub = all(warr)
if ub==1 c64scr.print("error all4\n")
ub = all(farr)
if ub==1 c64scr.print("error all5\n")
ubarr[1]=$40
barr[1]=$40
uwarr[1]=$4000
warr[1]=$4000
farr[1]=1.1
ub = all(ubarr)
if ub==0 c64scr.print("error all6\n")
ub = all(barr)
if ub==0 c64scr.print("error all7\n")
ub = all(uwarr)
if ub==0 c64scr.print("error all8\n")
ub = all(warr)
if ub==0 c64scr.print("error all9\n")
ub = all(farr)
if ub==0 c64scr.print("error all10\n")
c64scr.print("\nyou should see no errors above.")
}
}

View File

@ -0,0 +1,75 @@
%import c64utils
%zeropage basicsafe
main {
byte bb
ubyte ub
word ww
uword uw
&ubyte membyte=9999
&uword memword=9999
ubyte[10] barray
sub start() {
lsr(A)
lsl(A)
ror(A)
rol(A)
ror2(A)
rol2(A)
lsr(membyte)
lsl(membyte)
ror(membyte)
rol(membyte)
ror2(membyte)
rol2(membyte)
lsr(memword)
lsl(memword)
ror(memword)
rol(memword)
ror2(memword)
rol2(memword)
lsl(@(9999))
lsr(@(9999))
ror(@(9999))
rol(@(9999))
ror2(@(9999))
rol2(@(9999))
lsr(barray[1])
lsl(barray[1])
ror(barray[1])
rol(barray[1])
ror2(barray[1])
rol2(barray[1])
bb /= 2
bb >>= 1
bb *= 4
bb <<= 2
ub /= 2
ub >>= 1
ub *= 4
ub <<= 2
rol(ub)
ror(ub)
rol2(ub)
ror2(ub)
ww /= 2
ww >>= 1
ww *= 4
ww <<= 2
uw /= 2
uw >>= 1
uw *= 4
uw <<= 2
rol(uw)
ror(uw)
rol2(uw)
ror2(uw)
}
}

105
examples/arithmetic/div.p8 Normal file
View File

@ -0,0 +1,105 @@
%import c64lib
%import c64utils
%import c64flt
%zeropage basicsafe
main {
sub start() {
div_ubyte(0, 1, 0)
div_ubyte(100, 6, 16)
div_ubyte(255, 2, 127)
div_byte(0, 1, 0)
div_byte(100, -6, -16)
div_byte(127, -2, -63)
div_uword(0,1,0)
div_uword(40000,500,80)
div_uword(43211,2,21605)
div_word(0,1,0)
div_word(-20000,500,-40)
div_word(-2222,2,-1111)
div_float(0,1,0)
div_float(999.9,111.0,9.008108108108107)
}
sub div_ubyte(ubyte a1, ubyte a2, ubyte c) {
ubyte r = a1/a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("ubyte ")
c64scr.print_ub(a1)
c64scr.print(" / ")
c64scr.print_ub(a2)
c64scr.print(" = ")
c64scr.print_ub(r)
c64.CHROUT('\n')
}
sub div_byte(byte a1, byte a2, byte c) {
byte r = a1/a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("byte ")
c64scr.print_b(a1)
c64scr.print(" / ")
c64scr.print_b(a2)
c64scr.print(" = ")
c64scr.print_b(r)
c64.CHROUT('\n')
}
sub div_uword(uword a1, uword a2, uword c) {
uword r = a1/a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("uword ")
c64scr.print_uw(a1)
c64scr.print(" / ")
c64scr.print_uw(a2)
c64scr.print(" = ")
c64scr.print_uw(r)
c64.CHROUT('\n')
}
sub div_word(word a1, word a2, word c) {
word r = a1/a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("word ")
c64scr.print_w(a1)
c64scr.print(" / ")
c64scr.print_w(a2)
c64scr.print(" = ")
c64scr.print_w(r)
c64.CHROUT('\n')
}
sub div_float(float a1, float a2, float c) {
float r = a1/a2
if abs(r-c)<0.00001
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("float ")
c64flt.print_f(a1)
c64scr.print(" / ")
c64flt.print_f(a2)
c64scr.print(" = ")
c64flt.print_f(r)
c64.CHROUT('\n')
}
}

View File

@ -0,0 +1,113 @@
%import c64lib
%import c64utils
%import c64flt
%zeropage basicsafe
main {
sub start() {
minus_ubyte(0, 0, 0)
minus_ubyte(200, 0, 200)
minus_ubyte(200, 100, 100)
minus_ubyte(100, 200, 156)
minus_byte(0, 0, 0)
minus_byte(100, 100, 0)
minus_byte(50, -50, 100)
minus_byte(0, -30, 30)
minus_byte(-30, 0, -30)
minus_uword(0,0,0)
minus_uword(50000,0, 50000)
minus_uword(50000,20000,30000)
minus_uword(20000,50000,35536)
minus_word(0,0,0)
minus_word(1000,1000,0)
minus_word(-1000,1000,-2000)
minus_word(1000,500,500)
minus_word(0,-3333,3333)
minus_word(-3333,0,-3333)
minus_float(0,0,0)
minus_float(2.5,1.5,1.0)
minus_float(-1.5,3.5,-5.0)
}
sub minus_ubyte(ubyte a1, ubyte a2, ubyte c) {
ubyte r = a1-a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("ubyte ")
c64scr.print_ub(a1)
c64scr.print(" - ")
c64scr.print_ub(a2)
c64scr.print(" = ")
c64scr.print_ub(r)
c64.CHROUT('\n')
}
sub minus_byte(byte a1, byte a2, byte c) {
byte r = a1-a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("byte ")
c64scr.print_b(a1)
c64scr.print(" - ")
c64scr.print_b(a2)
c64scr.print(" = ")
c64scr.print_b(r)
c64.CHROUT('\n')
}
sub minus_uword(uword a1, uword a2, uword c) {
uword r = a1-a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("uword ")
c64scr.print_uw(a1)
c64scr.print(" - ")
c64scr.print_uw(a2)
c64scr.print(" = ")
c64scr.print_uw(r)
c64.CHROUT('\n')
}
sub minus_word(word a1, word a2, word c) {
word r = a1-a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("word ")
c64scr.print_w(a1)
c64scr.print(" - ")
c64scr.print_w(a2)
c64scr.print(" = ")
c64scr.print_w(r)
c64.CHROUT('\n')
}
sub minus_float(float a1, float a2, float c) {
float r = a1-a2
if abs(r-c)<0.00001
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("float ")
c64flt.print_f(a1)
c64scr.print(" - ")
c64flt.print_f(a2)
c64scr.print(" = ")
c64flt.print_f(r)
c64.CHROUT('\n')
}
}

107
examples/arithmetic/mult.p8 Normal file
View File

@ -0,0 +1,107 @@
%import c64lib
%import c64utils
%import c64flt
%zeropage basicsafe
main {
sub start() {
mul_ubyte(0, 0, 0)
mul_ubyte(20, 1, 20)
mul_ubyte(20, 10, 200)
mul_byte(0, 0, 0)
mul_byte(10, 10, 100)
mul_byte(5, -5, -25)
mul_byte(0, -30, 0)
mul_uword(0,0,0)
mul_uword(50000,1, 50000)
mul_uword(500,100,50000)
mul_word(0,0,0)
mul_word(-10,1000,-10000)
mul_word(1,-3333,-3333)
mul_float(0,0,0)
mul_float(2.5,10,25)
mul_float(-1.5,10,-15)
}
sub mul_ubyte(ubyte a1, ubyte a2, ubyte c) {
ubyte r = a1*a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("ubyte ")
c64scr.print_ub(a1)
c64scr.print(" * ")
c64scr.print_ub(a2)
c64scr.print(" = ")
c64scr.print_ub(r)
c64.CHROUT('\n')
}
sub mul_byte(byte a1, byte a2, byte c) {
byte r = a1*a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("byte ")
c64scr.print_b(a1)
c64scr.print(" * ")
c64scr.print_b(a2)
c64scr.print(" = ")
c64scr.print_b(r)
c64.CHROUT('\n')
}
sub mul_uword(uword a1, uword a2, uword c) {
uword r = a1*a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("uword ")
c64scr.print_uw(a1)
c64scr.print(" * ")
c64scr.print_uw(a2)
c64scr.print(" = ")
c64scr.print_uw(r)
c64.CHROUT('\n')
}
sub mul_word(word a1, word a2, word c) {
word r = a1*a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("word ")
c64scr.print_w(a1)
c64scr.print(" * ")
c64scr.print_w(a2)
c64scr.print(" = ")
c64scr.print_w(r)
c64.CHROUT('\n')
}
sub mul_float(float a1, float a2, float c) {
float r = a1*a2
if abs(r-c)<0.00001
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("float ")
c64flt.print_f(a1)
c64scr.print(" * ")
c64flt.print_f(a2)
c64scr.print(" = ")
c64flt.print_f(r)
c64.CHROUT('\n')
}
}

111
examples/arithmetic/plus.p8 Normal file
View File

@ -0,0 +1,111 @@
%import c64lib
%import c64utils
%import c64flt
%zeropage basicsafe
main {
sub start() {
plus_ubyte(0, 0, 0)
plus_ubyte(0, 200, 200)
plus_ubyte(100, 200, 44)
plus_byte(0, 0, 0)
plus_byte(-100, 100, 0)
plus_byte(-50, 100, 50)
plus_byte(0, -30, -30)
plus_byte(-30, 0, -30)
plus_uword(0,0,0)
plus_uword(0,50000,50000)
plus_uword(50000,20000,4464)
plus_word(0,0,0)
plus_word(-1000,1000,0)
plus_word(-500,1000,500)
plus_word(0,-3333,-3333)
plus_word(-3333,0,-3333)
plus_float(0,0,0)
plus_float(1.5,2.5,4.0)
plus_float(-1.5,3.5,2.0)
plus_float(-1.1,3.3,2.2)
}
sub plus_ubyte(ubyte a1, ubyte a2, ubyte c) {
ubyte r = a1+a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("ubyte ")
c64scr.print_ub(a1)
c64scr.print(" + ")
c64scr.print_ub(a2)
c64scr.print(" = ")
c64scr.print_ub(r)
c64.CHROUT('\n')
}
sub plus_byte(byte a1, byte a2, byte c) {
byte r = a1+a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("byte ")
c64scr.print_b(a1)
c64scr.print(" + ")
c64scr.print_b(a2)
c64scr.print(" = ")
c64scr.print_b(r)
c64.CHROUT('\n')
}
sub plus_uword(uword a1, uword a2, uword c) {
uword r = a1+a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("uword ")
c64scr.print_uw(a1)
c64scr.print(" + ")
c64scr.print_uw(a2)
c64scr.print(" = ")
c64scr.print_uw(r)
c64.CHROUT('\n')
}
sub plus_word(word a1, word a2, word c) {
word r = a1+a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("word ")
c64scr.print_w(a1)
c64scr.print(" + ")
c64scr.print_w(a2)
c64scr.print(" = ")
c64scr.print_w(r)
c64.CHROUT('\n')
}
sub plus_float(float a1, float a2, float c) {
float r = a1+a2
if abs(r-c)<0.00001
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("float ")
c64flt.print_f(a1)
c64scr.print(" + ")
c64flt.print_f(a2)
c64scr.print(" = ")
c64flt.print_f(r)
c64.CHROUT('\n')
}
}

View File

@ -0,0 +1,142 @@
%import c64utils
%import c64flt
%option enable_floats
%zeropage basicsafe
main {
sub start() {
c64scr.plot(0,24)
ubyte ub=200
byte bb=-100
uword uw = 2000
word ww = -1000
float fl = 999.99
ubyte[3] ubarr = 200
byte[3] barr = -100
uword[3] uwarr = 2000
word[3] warr = -1000
float[3] flarr = 999.99
c64scr.print("++\n")
ub++
bb++
uw++
ww++
fl++
ubarr[1]++
barr[1]++
uwarr[1]++
warr[1]++
flarr[1] ++
check_ub(ub, 201)
Y=100
Y++
check_ub(Y, 101)
check_fl(fl, 1000.99)
check_b(bb, -99)
check_uw(uw, 2001)
check_w(ww, -999)
check_ub(ubarr[0], 200)
check_fl(flarr[0], 999.99)
check_b(barr[0], -100)
check_uw(uwarr[0], 2000)
check_w(warr[0], -1000)
check_ub(ubarr[1], 201)
check_fl(flarr[1], 1000.99)
check_b(barr[1], -99)
check_uw(uwarr[1], 2001)
check_w(warr[1], -999)
c64scr.print("--\n")
ub--
bb--
uw--
ww--
fl--
ubarr[1]--
barr[1]--
uwarr[1]--
warr[1]--
flarr[1] --
check_ub(ub, 200)
Y=100
Y--
check_ub(Y, 99)
check_fl(fl, 999.99)
check_b(bb, -100)
check_uw(uw, 2000)
check_w(ww, -1000)
check_ub(ubarr[1], 200)
check_fl(flarr[1], 999.99)
check_b(barr[1], -100)
check_uw(uwarr[1], 2000)
check_w(warr[1], -1000)
@($0400+400-1) = X
}
sub check_ub(ubyte value, ubyte expected) {
if value==expected
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print(" ubyte ")
c64scr.print_ub(value)
c64.CHROUT(',')
c64scr.print_ub(expected)
c64.CHROUT('\n')
}
sub check_b(byte value, byte expected) {
if value==expected
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print(" byte ")
c64scr.print_b(value)
c64.CHROUT(',')
c64scr.print_b(expected)
c64.CHROUT('\n')
}
sub check_uw(uword value, uword expected) {
if value==expected
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print(" uword ")
c64scr.print_uw(value)
c64.CHROUT(',')
c64scr.print_uw(expected)
c64.CHROUT('\n')
}
sub check_w(word value, word expected) {
if value==expected
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print(" word ")
c64scr.print_w(value)
c64.CHROUT(',')
c64scr.print_w(expected)
c64.CHROUT('\n')
}
sub check_fl(float value, float expected) {
if value==expected
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print(" float ")
c64flt.print_f(value)
c64.CHROUT(',')
c64flt.print_f(expected)
c64.CHROUT('\n')
}
}

View File

@ -0,0 +1,49 @@
%import c64lib
%import c64utils
%import c64flt
%zeropage basicsafe
main {
sub start() {
remainder_ubyte(0, 1, 0)
remainder_ubyte(100, 6, 4)
remainder_ubyte(255, 2, 1)
remainder_ubyte(255, 20, 15)
remainder_uword(0,1,0)
remainder_uword(40000,511,142)
remainder_uword(40000,500,0)
remainder_uword(43211,12,11)
}
sub remainder_ubyte(ubyte a1, ubyte a2, ubyte c) {
ubyte r = a1%a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("ubyte ")
c64scr.print_ub(a1)
c64scr.print(" % ")
c64scr.print_ub(a2)
c64scr.print(" = ")
c64scr.print_ub(r)
c64.CHROUT('\n')
}
sub remainder_uword(uword a1, uword a2, uword c) {
uword r = a1%a2
if r==c
c64scr.print(" ok ")
else
c64scr.print("err! ")
c64scr.print("uword ")
c64scr.print_uw(a1)
c64scr.print(" % ")
c64scr.print_uw(a2)
c64scr.print(" = ")
c64scr.print_uw(r)
c64.CHROUT('\n')
}
}

View File

@ -1,6 +1,6 @@
%import c64lib
~ main {
main {
sub start() {
@ -18,10 +18,10 @@ sub start() {
while(true) {
for uword note in notes {
ubyte n1 = lsb(note)
ubyte n2 = msb(note)
c64.FREQ1 = music_freq_table[n1] ; set lo+hi freq of voice 1
c64.FREQ2 = music_freq_table[n2] ; set lo+hi freq of voice 2
ubyte note1 = lsb(note)
ubyte note2 = msb(note)
c64.FREQ1 = music_freq_table[note1] ; set lo+hi freq of voice 1
c64.FREQ2 = music_freq_table[note2] ; set lo+hi freq of voice 2
; retrigger voice 1 and 2 ADSR
c64.CR1 = waveform <<4 | 0
@ -29,7 +29,7 @@ sub start() {
c64.CR1 = waveform <<4 | 1
c64.CR2 = waveform <<4 | 1
print_notes(n1, n2)
print_notes(note1, note2)
delay()
}
}

View File

@ -2,7 +2,7 @@
%zeropage basicsafe
~ main {
main {
sub start() {

View File

@ -2,7 +2,7 @@
%import c64flt
%zeropage basicsafe
~ main {
main {
sub start() {

View File

@ -2,7 +2,7 @@
%zeropage basicsafe
~ main {
main {
sub start() {

View File

@ -2,7 +2,7 @@
%zeropage basicsafe
~ main {
main {
sub start() {

View File

@ -2,7 +2,7 @@
%zeropage basicsafe
~ main {
main {
sub start() {

View File

@ -1,7 +1,7 @@
%import c64utils
%zeropage basicsafe
~ main {
main {
sub start() {

View File

@ -2,7 +2,7 @@
%import c64flt
%zeropage basicsafe
~ main {
main {
sub start() {

View File

@ -1,7 +1,7 @@
%import c64utils
%zeropage basicsafe
~ main {
main {
sub start() {

View File

@ -1,7 +1,7 @@
%import c64utils
%zeropage basicsafe
~ main {
main {
sub start() {

View File

@ -1,7 +1,7 @@
%import c64utils
%zeropage basicsafe
~ main {
main {
sub start() {

View File

@ -2,7 +2,7 @@
%import c64utils
%import c64flt
~ main {
main {
const uword width = 40
const uword height = 25
@ -66,23 +66,28 @@
; plot the points of the 3d cube
; first the points on the back, then the points on the front (painter algorithm)
ubyte i
float rz
float persp
ubyte sx
ubyte sy
for ubyte i in 0 to len(xcoor)-1 {
float rz = rotatedz[i]
for i in 0 to len(xcoor)-1 {
rz = rotatedz[i]
if rz >= 0.1 {
float persp = (5.0+rz)/height
ubyte sx = rotatedx[i] / persp + width/2.0 as ubyte
ubyte sy = rotatedy[i] / persp + height/2.0 as ubyte
persp = (5.0+rz)/height
sx = rotatedx[i] / persp + width/2.0 as ubyte
sy = rotatedy[i] / persp + height/2.0 as ubyte
c64scr.setcc(sx, sy, 46, i+2)
}
}
for ubyte i in 0 to len(xcoor)-1 {
float rz = rotatedz[i]
for i in 0 to len(xcoor)-1 {
rz = rotatedz[i]
if rz < 0.1 {
float persp = (5.0+rz)/height
ubyte sx = rotatedx[i] / persp + width/2.0 as ubyte
ubyte sy = rotatedy[i] / persp + height/2.0 as ubyte
persp = (5.0+rz)/height
sx = rotatedx[i] / persp + width/2.0 as ubyte
sy = rotatedy[i] / persp + height/2.0 as ubyte
c64scr.setcc(sx, sy, 81, i+2)
}
}

View File

@ -1,7 +1,7 @@
%import c64lib
%import c64utils
~ spritedata $2000 {
spritedata $2000 {
; this memory block contains the sprite data
; it must start on an address aligned to 64 bytes.
%option force_output ; make sure the data in this block appears in the resulting program
@ -58,7 +58,7 @@
}
~ main {
main {
const uword width = 255
const uword height = 200

View File

@ -1,122 +0,0 @@
%output raw
%launcher none
%import c64flt
~ irq {
uword global_time
ubyte time_changed
sub irq() {
; activated automatically if run in StackVm
global_time++
time_changed = 1
}
}
~ main {
const uword width = 320
const uword height = 200
; vertices
float[] xcoor = [ -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0 ]
float[] ycoor = [ -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0 ]
float[] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ]
; edges (msb=from vertex, lsb=to vertex)
uword[] edges = [$0001, $0103, $0302, $0200, $0405, $0507, $0706, $0604, $0004, $0105, $0206, $0307]
; storage for rotated coordinates
float[len(xcoor)] rotatedx
float[len(ycoor)] rotatedy
float[len(zcoor)] rotatedz
sub start() {
while true {
if irq.time_changed {
irq.time_changed = 0
vm_gfx_clearscr(0)
vm_gfx_text(8, 6, 1, "Spin")
vm_gfx_text(29, 11, 1, "to Win !")
for uword i in 0 to width/10 {
vm_gfx_line(i*2+width/2-width/10, 130, i*10.w, 199, 6)
}
rotate_vertices(irq.global_time as float / 30.0)
draw_edges()
}
}
}
sub rotate_vertices(float t) {
; rotate around origin (0,0,0)
; set up the 3d rotation matrix values
float cosa = cos(t)
float sina = sin(t)
float cosb = cos(t*0.33)
float sinb = sin(t*0.33)
float cosc = cos(t*0.78)
float sinc = sin(t*0.78)
float cosa_sinb = cosa*sinb
float sina_sinb = sina*sinb
float Axx = cosa*cosb
float Axy = cosa_sinb*sinc - sina*cosc
float Axz = cosa_sinb*cosc + sina*sinc
float Ayx = sina*cosb
float Ayy = sina_sinb*sinc + cosa*cosc
float Ayz = sina_sinb*cosc - cosa*sinc
float Azx = -sinb
float Azy = cosb*sinc
float Azz = cosb*cosc
for ubyte i in 0 to len(xcoor)-1 {
rotatedx[i] = Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i]
rotatedy[i] = Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i]
rotatedz[i] = Azx*xcoor[i] + Azy*ycoor[i] + Azz*zcoor[i]
}
}
sub draw_edges() {
sub toscreenx(float x, float z) -> word {
return x/(4.2+z) * (height as float) as word + width / 2
}
sub toscreeny(float y, float z) -> word {
return y/(4.2+z) * (height as float) as word + height / 2
}
; draw all edges of the object
for uword edge in edges {
ubyte e_from = msb(edge)
ubyte e_to = lsb(edge)
vm_gfx_line(toscreenx(rotatedx[e_from], rotatedz[e_from]), toscreeny(rotatedy[e_from], rotatedz[e_from]),
toscreenx(rotatedx[e_to], rotatedz[e_to]), toscreeny(rotatedy[e_to], rotatedz[e_to]), e_from+e_to)
}
; accentuate the vertices a bit with small boxes
for ubyte i in 0 to len(xcoor)-1 {
word sx = toscreenx(rotatedx[i], rotatedz[i])
word sy = toscreeny(rotatedy[i], rotatedz[i])
ubyte color=i+2
vm_gfx_pixel(sx-1, sy-1, color)
vm_gfx_pixel(sx, sy-1, color)
vm_gfx_pixel(sx+1, sy-1, color)
vm_gfx_pixel(sx-1, sy, color)
vm_gfx_pixel(sx, sy, color)
vm_gfx_pixel(sx+1, sy, color)
vm_gfx_pixel(sx-1, sy+1, color)
vm_gfx_pixel(sx, sy+1, color)
vm_gfx_pixel(sx+1, sy+1, color)
vm_gfx_pixel(sx, sy-2, color)
vm_gfx_pixel(sx+2, sy, color)
vm_gfx_pixel(sx, sy+2, color)
vm_gfx_pixel(sx-2, sy, color)
}
}
}

View File

@ -1,7 +1,7 @@
%import c64lib
%import c64utils
~ main {
main {
const uword width = 40
const uword height = 25
@ -75,22 +75,28 @@
; plot the points of the 3d cube
; first the points on the back, then the points on the front (painter algorithm)
for ubyte i in 0 to len(xcoor)-1 {
word rz = rotatedz[i]
ubyte i
word rz
word persp
byte sx
byte sy
for i in 0 to len(xcoor)-1 {
rz = rotatedz[i]
if rz >= 10 {
word persp = (rz+200) / height
byte sx = rotatedx[i] / persp as byte + width/2
byte sy = rotatedy[i] / persp as byte + height/2
persp = (rz+200) / height
sx = rotatedx[i] / persp as byte + width/2
sy = rotatedy[i] / persp as byte + height/2
c64scr.setcc(sx as ubyte, sy as ubyte, 46, vertexcolors[(rz as byte >>5) + 3])
}
}
for ubyte i in 0 to len(xcoor)-1 {
word rz = rotatedz[i]
for i in 0 to len(xcoor)-1 {
rz = rotatedz[i]
if rz < 10 {
word persp = (rz+200) / height
byte sx = rotatedx[i] / persp as byte + width/2
byte sy = rotatedy[i] / persp as byte + height/2
persp = (rz+200) / height
sx = rotatedx[i] / persp as byte + width/2
sy = rotatedy[i] / persp as byte + height/2
c64scr.setcc(sx as ubyte, sy as ubyte, 81, vertexcolors[(rz as byte >>5) + 3])
}
}

View File

@ -7,11 +7,11 @@
; This is extremely handy for the Fibonacci sequence because it is defined
; in terms of 'the next value is the sum of the previous two values'
~ main {
main {
sub start() {
c64scr.print("fibonacci sequence\n")
fib_setup()
for ubyte i in 0 to 20 {
for A in 0 to 20 {
c64scr.print_uw(fib_next())
c64.CHROUT('\n')
}

View File

@ -4,7 +4,7 @@
%zeropage basicsafe
~ main {
main {
sub start() {

View File

@ -1,40 +0,0 @@
%output raw
%launcher none
%import c64flt
~ main {
const uword width = 320 / 2
const uword height = 256 / 2
const uword xoffset = 40
const uword yoffset = 30
sub start() {
vm_gfx_clearscr(11)
vm_gfx_text(2, 1, 1, "Calculating Mandelbrot Fractal...")
for ubyte pixely in yoffset to yoffset+height-1 {
float yy = (pixely-yoffset as float)/3.6/height+0.4
for uword pixelx in xoffset to xoffset+width-1 {
float xx = (pixelx-xoffset as float)/3.0/width+0.2
float xsquared = 0.0
float ysquared = 0.0
float x = 0.0
float y = 0.0
ubyte iter = 0
while (iter<32 and xsquared+ysquared<4.0) {
y = x*y*2.0 + yy
x = xsquared - ysquared + xx
xsquared = x*x
ysquared = y*y
iter++
}
vm_gfx_pixel(pixelx, pixely, iter)
}
}
vm_gfx_text(11, 21, 1, "Finished!")
}
}

View File

@ -4,7 +4,7 @@
%zeropage basicsafe
~ main {
main {
const uword width = 30
const uword height = 20
const ubyte max_iter = 16

View File

@ -4,7 +4,7 @@
; The classic number guessing game.
~ main {
main {
sub start() {
str name = "????????????????????????????????????????"

View File

@ -1,7 +1,7 @@
%import c64utils
%zeropage basicsafe
~ main {
main {
ubyte[256] sieve
ubyte candidate_prime = 2 ; is increased in the loop

View File

@ -2,7 +2,7 @@
%import c64lib
~ main {
main {
sub start() {
c64.SCROLY &= %11101111 ; blank the screen
@ -16,7 +16,7 @@
}
~ irq {
irq {
const ubyte barheight = 4
ubyte[] colors = [6,2,4,5,15,7,1,13,3,12,8,11,9]

56
examples/romfloats.p8 Normal file
View File

@ -0,0 +1,56 @@
%import c64flt
%zeropage basicsafe
%option enable_floats
main {
sub start() {
; these are all floating point constants defined in the ROM so no allocation required
c64flt.print_f(3.141592653589793)
c64.CHROUT('\n')
c64flt.print_f(-32768.0)
c64.CHROUT('\n')
c64flt.print_f( 1.0)
c64.CHROUT('\n')
c64flt.print_f(0.7071067811865476)
c64.CHROUT('\n')
c64flt.print_f(1.4142135623730951)
c64.CHROUT('\n')
c64flt.print_f( -0.5)
c64.CHROUT('\n')
c64flt.print_f(0.6931471805599453)
c64.CHROUT('\n')
c64flt.print_f(10.0)
c64.CHROUT('\n')
c64flt.print_f(1.0e9)
c64.CHROUT('\n')
c64flt.print_f(0.5)
c64.CHROUT('\n')
c64flt.print_f(1.4426950408889634)
c64.CHROUT('\n')
c64flt.print_f(1.5707963267948966)
c64.CHROUT('\n')
c64flt.print_f(6.283185307179586)
c64.CHROUT('\n')
c64flt.print_f(0.25)
c64.CHROUT('\n')
c64flt.print_f(0.0)
c64.CHROUT('\n')
}
}

View File

@ -3,7 +3,7 @@
%zeropage basicsafe
~ spritedata $0a00 {
spritedata $0a00 {
; this memory block contains the sprite data
; it must start on an address aligned to 64 bytes.
%option force_output ; make sure the data in this block appears in the resulting program
@ -31,7 +31,7 @@
%00000000,%00011100,%00000000 ]
}
~ main {
main {
const uword SP0X = $d000
const uword SP0Y = $d001
@ -52,7 +52,7 @@
}
~ irq {
irq {
sub irq() {
c64.EXTCOL--

View File

@ -1,7 +1,7 @@
%import c64utils
%zeropage basicsafe
~ main {
main {
struct Color {
ubyte red

View File

@ -1,7 +1,7 @@
%import c64utils
%import c64flt
~ main {
main {
const uword width = 40
const uword height = 25

View File

@ -1,32 +0,0 @@
%output raw
%launcher none
%import c64flt
~ main {
const uword width = 320
const uword height = 200
sub start() {
vm_gfx_clearscr(0)
float t
ubyte color
while true {
float x = sin(t*1.01) + cos(t*1.1234)
float y = cos(t) + sin(t*0.03456)
vm_gfx_pixel(screenx(x), screeny(y), color/16)
t += 0.01
color++
}
}
sub screenx(float x) -> word {
return (x*width/4.1) + width / 2.0 as word
}
sub screeny(float y) -> word {
return (y*height/4.1) + height / 2.0 as word
}
}

View File

@ -1,6 +1,6 @@
%import c64utils
~ main {
main {
const uword width = 40
const uword height = 25

View File

@ -10,7 +10,7 @@
; @todo show ghost?
~ main {
main {
const ubyte boardOffsetX = 14
const ubyte boardOffsetY = 3
@ -189,8 +189,9 @@ waitkey:
; check if line(s) are full -> flash/clear line(s) + add score + move rest down
ubyte[boardHeight] complete_lines
ubyte num_lines=0
ubyte linepos
memset(complete_lines, len(complete_lines), 0)
for ubyte linepos in boardOffsetY to boardOffsetY+boardHeight-1 {
for linepos in boardOffsetY to boardOffsetY+boardHeight-1 {
if blocklogic.isLineFull(linepos) {
complete_lines[num_lines]=linepos
num_lines++
@ -208,7 +209,7 @@ waitkey:
; slight delay to flash the line
}
c64.TIME_LO=0
for ubyte linepos in complete_lines
for linepos in complete_lines
if linepos and blocklogic.isLineFull(linepos)
blocklogic.collapse(linepos)
lines += num_lines
@ -387,7 +388,7 @@ waitkey:
}
~ blocklogic {
blocklogic {
ubyte currentBlockNum
ubyte[16] currentBlock
@ -561,7 +562,7 @@ waitkey:
}
~ sound {
sound {
sub init() {
c64.MVOL = 15

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