mirror of
https://github.com/irmen/prog8.git
synced 2025-06-15 02:23:36 +00:00
Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
68a7f9c665 | |||
ffd8d9c7c1 | |||
c66fc8630c | |||
9ca1c66f2b | |||
33647a29d0 | |||
02b12cc762 | |||
3280993e2a | |||
3723c22054 | |||
0a2c4ea0c4 | |||
58a83c0439 | |||
d665489054 | |||
9200992024 | |||
6408cc46a8 | |||
961bcdb7ae | |||
edee70cf31 | |||
1978a9815a | |||
f5e6db9d66 | |||
a94bc40ab0 | |||
534b5ced8f | |||
5ebd9b54e4 | |||
cc4e272526 | |||
295e199bfa | |||
df3371b0f0 | |||
e4fe1d2b8d | |||
b8b9244ffa | |||
3be3989e1c | |||
ed54cf680a | |||
95e76058d3 | |||
a6bee6a860 | |||
d22780ee44 | |||
f8b0b9575d | |||
4274fd168e | |||
be7f5957f3 | |||
f2e5d987a9 | |||
f01173d8db | |||
15e8e0bf6d | |||
2c59cbdece | |||
b73da4ed02 | |||
267adb4612 |
@ -31,13 +31,13 @@ which aims to provide many conveniences over raw assembly code (even when using
|
||||
|
||||
Rapid edit-compile-run-debug cycle:
|
||||
|
||||
- use modern PC to work on
|
||||
- quick compilation times (seconds)
|
||||
- option to automatically run the program in the Vice emulator
|
||||
- use a modern PC to do the work on
|
||||
- very quick compilation times
|
||||
- can automatically run the program in the Vice emulator after succesful compilation
|
||||
- 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
|
||||
|
||||
It is mainly targeted at the Commodore-64 machine at this time.
|
||||
Prog8 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/manual
|
||||
|
@ -1,11 +1,11 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.70"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
// id "org.jetbrains.kotlin.jvm" version "1.3.70"
|
||||
// id "org.jetbrains.kotlin.jvm" version "1.3.72"
|
||||
id 'application'
|
||||
id 'org.jetbrains.dokka' version "0.9.18"
|
||||
id 'com.github.johnrengelman.shadow' version '5.2.0'
|
||||
|
@ -658,8 +658,8 @@ func_all_f .proc
|
||||
dey
|
||||
cmp #0
|
||||
beq +
|
||||
cpy #255
|
||||
bne -
|
||||
cpy #255
|
||||
bne -
|
||||
lda #1
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
@ -739,3 +739,45 @@ sign_f .proc
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
set_0_array_float .proc
|
||||
; -- set a float in an array to zero (index on stack, array in SCRATCH_ZPWORD1)
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.ESTACK_LO,x
|
||||
tay
|
||||
lda #0
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
iny
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
iny
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
iny
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
iny
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
set_array_float .proc
|
||||
; -- set a float in an array to a value (index on stack, float in SCRATCH_ZPWORD1, array in SCRATCH_ZPWORD2)
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.ESTACK_LO,x
|
||||
clc
|
||||
adc c64.SCRATCH_ZPWORD2
|
||||
ldy c64.SCRATCH_ZPWORD2+1
|
||||
bcc +
|
||||
iny
|
||||
+ jmp copy_float
|
||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||
.pend
|
||||
|
@ -1772,7 +1772,6 @@ _loop_hi ldy _index_first
|
||||
|
||||
ror2_mem_ub .proc
|
||||
; -- in-place 8-bit ror of byte at memory location on stack
|
||||
; TODO use self modifying code here
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
@ -1789,7 +1788,6 @@ ror2_mem_ub .proc
|
||||
|
||||
rol2_mem_ub .proc
|
||||
; -- in-place 8-bit rol of byte at memory location on stack
|
||||
; TODO use self modifying code here
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
@ -1804,57 +1802,279 @@ rol2_mem_ub .proc
|
||||
.pend
|
||||
|
||||
lsl_array_b .proc
|
||||
.warn "lsl_array_b" ; TODO
|
||||
.pend
|
||||
|
||||
lsl_array_w .proc
|
||||
.warn "lsl_array_w" ; TODO
|
||||
; -- lsl a (u)byte in an array (index and array address on stack)
|
||||
inx
|
||||
ldy c64.ESTACK_LO,x
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
asl a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
lsr_array_ub .proc
|
||||
.warn "lsr_array_ub" ; TODO
|
||||
; -- lsr a ubyte in an array (index and array address on stack)
|
||||
inx
|
||||
ldy c64.ESTACK_LO,x
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
lsr a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
lsr_array_b .proc
|
||||
.warn "lsr_array_b" ; TODO
|
||||
; -- lsr a byte in an array (index and array address on stack)
|
||||
inx
|
||||
ldy c64.ESTACK_LO,x
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
asl a
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
ror a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
lsl_array_w .proc
|
||||
; -- lsl a (u)word in an array (index and array address on stack)
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
asl a
|
||||
tay
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
asl a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
rol a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
lsr_array_uw .proc
|
||||
.warn "lsr_array_uw" ; TODO
|
||||
; -- lsr a uword in an array (index and array address on stack)
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
asl a
|
||||
tay
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
lsr a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
dey
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
ror a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
lsr_array_w .proc
|
||||
.warn "lsr_array_w" ; TODO
|
||||
; -- lsr a uword in an array (index and array address on stack)
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
asl a
|
||||
tay
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
asl a
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
ror a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
dey
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
ror a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
rol_array_ub .proc
|
||||
.warn "rol_array_ub" ; TODO
|
||||
; -- rol a ubyte in an array (index and array address on stack)
|
||||
inx
|
||||
ldy c64.ESTACK_LO,x
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
rol a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
rol_array_uw .proc
|
||||
.warn "rol_array_uw" ; TODO
|
||||
.pend
|
||||
|
||||
rol2_array_ub .proc
|
||||
.warn "rol2_array_ub" ; TODO
|
||||
.pend
|
||||
|
||||
rol2_array_uw .proc
|
||||
.warn "rol2_array_uw" ; TODO
|
||||
.pend
|
||||
|
||||
ror_array_ub .proc
|
||||
.warn "ror_array_ub" ; TODO
|
||||
.pend
|
||||
|
||||
ror_array_uw .proc
|
||||
.warn "ror_array_uw" ; TODO
|
||||
; -- ror a ubyte in an array (index and array address on stack)
|
||||
inx
|
||||
ldy c64.ESTACK_LO,x
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
ror a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
ror2_array_ub .proc
|
||||
.warn "ror2_array_ub" ; TODO
|
||||
; -- ror2 (8-bit ror) a ubyte in an array (index and array address on stack)
|
||||
inx
|
||||
ldy c64.ESTACK_LO,x
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
lsr a
|
||||
bcc +
|
||||
ora #$80
|
||||
+ sta (c64.SCRATCH_ZPWORD1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
rol2_array_ub .proc
|
||||
; -- rol2 (8-bit rol) a ubyte in an array (index and array address on stack)
|
||||
inx
|
||||
ldy c64.ESTACK_LO,x
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
cmp #$80
|
||||
rol a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
ror_array_uw .proc
|
||||
; -- ror a uword in an array (index and array address on stack)
|
||||
php
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
asl a
|
||||
tay
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
plp
|
||||
ror a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
dey
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
ror a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
rol_array_uw .proc
|
||||
; -- rol a uword in an array (index and array address on stack)
|
||||
php
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
asl a
|
||||
tay
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
plp
|
||||
rol a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
rol a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
rol2_array_uw .proc
|
||||
; -- rol2 (16-bit rol) a uword in an array (index and array address on stack)
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
asl a
|
||||
tay
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
asl a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
rol a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
bcc +
|
||||
dey
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
adc #0
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
+ rts
|
||||
.pend
|
||||
|
||||
ror2_array_uw .proc
|
||||
.warn "ror2_array_uw" ; TODO
|
||||
; -- ror2 (16-bit ror) a uword in an array (index and array address on stack)
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
asl a
|
||||
tay
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
lsr a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
dey
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
ror a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
bcc +
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
ora #$80
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
+ rts
|
||||
.pend
|
||||
|
@ -1 +1 @@
|
||||
1.90
|
||||
2.1
|
||||
|
@ -177,8 +177,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
|
||||
private fun outputStatements(statements: List<Statement>) {
|
||||
for(stmt in statements) {
|
||||
if(stmt is VarDecl && stmt.autogeneratedDontRemove)
|
||||
continue // skip autogenerated decls (to avoid generating a newline)
|
||||
outputi("")
|
||||
stmt.accept(this)
|
||||
output("\n")
|
||||
@ -284,7 +282,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
|
||||
override fun visit(assignment: Assignment) {
|
||||
assignment.target.accept(this)
|
||||
if (assignment.aug_op != null)
|
||||
if (assignment.aug_op != null && assignment.aug_op != "setvalue")
|
||||
output(" ${assignment.aug_op} ")
|
||||
else
|
||||
output(" = ")
|
||||
|
@ -53,32 +53,31 @@ interface INameScope {
|
||||
|
||||
fun linkParents(parent: Node)
|
||||
|
||||
fun subScopes(): Map<String, INameScope> {
|
||||
val subscopes = mutableMapOf<String, INameScope>()
|
||||
fun subScope(name: String): INameScope? {
|
||||
for(stmt in statements) {
|
||||
when(stmt) {
|
||||
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
|
||||
is ForLoop -> subscopes[stmt.body.name] = stmt.body
|
||||
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body
|
||||
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
|
||||
is ForLoop -> if(stmt.body.name==name) return stmt.body
|
||||
is RepeatLoop -> if(stmt.body.name==name) return stmt.body
|
||||
is WhileLoop -> if(stmt.body.name==name) return stmt.body
|
||||
is BranchStatement -> {
|
||||
subscopes[stmt.truepart.name] = stmt.truepart
|
||||
if(stmt.elsepart.containsCodeOrVars())
|
||||
subscopes[stmt.elsepart.name] = stmt.elsepart
|
||||
if(stmt.truepart.name==name) return stmt.truepart
|
||||
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
|
||||
}
|
||||
is IfStatement -> {
|
||||
subscopes[stmt.truepart.name] = stmt.truepart
|
||||
if(stmt.elsepart.containsCodeOrVars())
|
||||
subscopes[stmt.elsepart.name] = stmt.elsepart
|
||||
if(stmt.truepart.name==name) return stmt.truepart
|
||||
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
|
||||
}
|
||||
is WhenStatement -> {
|
||||
stmt.choices.forEach { subscopes[it.statements.name] = it.statements }
|
||||
val scope = stmt.choices.firstOrNull { it.statements.name==name }
|
||||
if(scope!=null)
|
||||
return scope.statements
|
||||
}
|
||||
is INameScope -> subscopes[stmt.name] = stmt
|
||||
is INameScope -> if(stmt.name==name) return stmt
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
return subscopes
|
||||
return null
|
||||
}
|
||||
|
||||
fun getLabelOrVariable(name: String): Statement? {
|
||||
@ -126,7 +125,7 @@ interface INameScope {
|
||||
for(module in localContext.definingModule().program.modules) {
|
||||
var scope: INameScope? = module
|
||||
for(name in scopedName.dropLast(1)) {
|
||||
scope = scope?.subScopes()?.get(name)
|
||||
scope = scope?.subScope(name)
|
||||
if(scope==null)
|
||||
break
|
||||
}
|
||||
@ -134,7 +133,7 @@ interface INameScope {
|
||||
val result = scope.getLabelOrVariable(scopedName.last())
|
||||
if(result!=null)
|
||||
return result
|
||||
return scope.subScopes()[scopedName.last()] as Statement?
|
||||
return scope.subScope(scopedName.last()) as Statement?
|
||||
}
|
||||
}
|
||||
return null
|
||||
@ -146,7 +145,7 @@ interface INameScope {
|
||||
val result = localScope.getLabelOrVariable(scopedName[0])
|
||||
if (result != null)
|
||||
return result
|
||||
val subscope = localScope.subScopes()[scopedName[0]] as Statement?
|
||||
val subscope = localScope.subScope(scopedName[0]) as Statement?
|
||||
if (subscope != null)
|
||||
return subscope
|
||||
// not found in this scope, look one higher up
|
||||
@ -212,7 +211,7 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
|
||||
return if(mainBlocks.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
mainBlocks[0].subScopes()["start"] as Subroutine?
|
||||
mainBlocks[0].subScope("start") as Subroutine?
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,6 +232,7 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
|
||||
require(node is Module && replacement is Module)
|
||||
val idx = modules.indexOf(node)
|
||||
modules[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,6 +259,7 @@ class Module(override val name: String,
|
||||
require(node is Statement && replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)"
|
||||
|
@ -24,7 +24,7 @@ enum class DataType {
|
||||
* is the type assignable to the given other type?
|
||||
*/
|
||||
infix fun isAssignableTo(targetType: DataType) =
|
||||
// what types are assignable to others without loss of precision?
|
||||
// what types are assignable to others, perhaps via a typecast, without loss of precision?
|
||||
when(this) {
|
||||
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
|
||||
BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
|
||||
@ -150,7 +150,9 @@ object ParentSentinel : Node {
|
||||
override val position = Position("<<sentinel>>", 0, 0, 0)
|
||||
override var parent: Node = this
|
||||
override fun linkParents(parent: Node) {}
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {}
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
replacement.parent = this
|
||||
}
|
||||
}
|
||||
|
||||
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
||||
|
@ -4,7 +4,8 @@ import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.processing.*
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.target.AsmVariableAndReturnsPreparer
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
import prog8.optimizer.AssignmentTransformer
|
||||
import prog8.optimizer.FlattenAnonymousScopesAndNopRemover
|
||||
|
||||
|
||||
@ -13,19 +14,16 @@ internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: Err
|
||||
checker.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.prepareAsmVariablesAndReturns(errors: ErrorReporter) {
|
||||
val fixer = AsmVariableAndReturnsPreparer(this, errors)
|
||||
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
|
||||
val fixer = BeforeAsmGenerationAstChanger(this, errors)
|
||||
fixer.visit(this)
|
||||
fixer.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.reorderStatements() {
|
||||
val initvalueCreator = AddressOfInserter(this)
|
||||
initvalueCreator.visit(this)
|
||||
initvalueCreator.applyModifications()
|
||||
|
||||
val checker = StatementReorderer(this)
|
||||
checker.visit(this)
|
||||
val reorder = StatementReorderer(this)
|
||||
reorder.visit(this)
|
||||
reorder.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.addTypecasts(errors: ErrorReporter) {
|
||||
@ -34,6 +32,17 @@ internal fun Program.addTypecasts(errors: ErrorReporter) {
|
||||
caster.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.transformAssignments(errors: ErrorReporter) {
|
||||
val transform = AssignmentTransformer(this, errors)
|
||||
transform.visit(this)
|
||||
while(transform.optimizationsDone>0 && errors.isEmpty()) {
|
||||
transform.applyModifications()
|
||||
transform.optimizationsDone = 0
|
||||
transform.visit(this)
|
||||
}
|
||||
transform.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Module.checkImportedValid() {
|
||||
val imr = ImportedModuleDirectiveRemover()
|
||||
imr.visit(this, this.parent)
|
||||
|
@ -61,6 +61,7 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(node === expression && replacement is Expression)
|
||||
expression = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
@ -112,6 +113,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
node===right -> right = replacement
|
||||
else -> throw FatalAstException("invalid replace, no child $node")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
@ -231,6 +233,7 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
|
||||
node===arrayspec.index -> arrayspec.index = replacement as Expression
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
@ -268,6 +271,7 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===expression)
|
||||
expression = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@ -299,6 +303,7 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is IdentifierReference && node===identifier)
|
||||
identifier = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
@ -320,6 +325,7 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===addressExpression)
|
||||
addressExpression = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@ -535,6 +541,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
require(replacement is Expression)
|
||||
val idx = value.indexOf(node)
|
||||
value[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
|
||||
@ -629,6 +636,7 @@ class RangeExpr(var from: Expression,
|
||||
step===node -> step=replacement
|
||||
else -> throw FatalAstException("invalid replacement")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
@ -807,6 +815,7 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
val idx = args.indexOf(node)
|
||||
args[idx] = replacement as Expression
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program) = constValue(program, true)
|
||||
|
@ -1,93 +0,0 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.IterableDatatypes
|
||||
import prog8.ast.base.PassByReferenceDatatypes
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Statement
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.functions.FSignature
|
||||
|
||||
|
||||
internal class AddressOfInserter(val program: Program): AstWalker() {
|
||||
// Insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||
// TODO join this into the StatementReorderer?
|
||||
|
||||
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
// insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||
var parentStatement: Node = functionCall
|
||||
while(parentStatement !is Statement)
|
||||
parentStatement = parentStatement.parent
|
||||
val targetStatement = functionCall.target.targetSubroutine(program.namespace)
|
||||
if(targetStatement!=null) {
|
||||
return addAddressOfExprIfNeeded(targetStatement, functionCall.args, functionCall)
|
||||
} else {
|
||||
val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")]
|
||||
if(builtinFunc!=null)
|
||||
return addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.args, functionCall)
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
// insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||
val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||
if(targetStatement!=null) {
|
||||
return addAddressOfExprIfNeeded(targetStatement, functionCallStatement.args, functionCallStatement)
|
||||
} else {
|
||||
val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")]
|
||||
if(builtinFunc!=null)
|
||||
return addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.args, functionCallStatement)
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, args: MutableList<Expression>, parent: IFunctionCall): Iterable<IAstModification> {
|
||||
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
|
||||
val replacements = mutableListOf<IAstModification>()
|
||||
for(argparam in subroutine.parameters.withIndex().zip(args)) {
|
||||
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type == DataType.STR) {
|
||||
if(argparam.second is AddressOf)
|
||||
continue
|
||||
val idref = argparam.second as? IdentifierReference
|
||||
if(idref!=null) {
|
||||
val variable = idref.targetVarDecl(program.namespace)
|
||||
if(variable!=null && variable.datatype in IterableDatatypes) {
|
||||
replacements += IAstModification.ReplaceNode(
|
||||
args[argparam.first.index],
|
||||
AddressOf(idref, idref.position),
|
||||
parent as Node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return replacements
|
||||
}
|
||||
|
||||
private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FSignature, args: MutableList<Expression>, parent: IFunctionCall): Iterable<IAstModification> {
|
||||
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
|
||||
val replacements = mutableListOf<IAstModification>()
|
||||
for(arg in args.withIndex().zip(signature.parameters)) {
|
||||
val argvalue = arg.first.value
|
||||
val argDt = argvalue.inferType(program)
|
||||
if(argDt.typeOrElse(DataType.UBYTE) in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) {
|
||||
if(argvalue !is IdentifierReference)
|
||||
throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue")
|
||||
replacements += IAstModification.ReplaceNode(
|
||||
args[arg.first.index],
|
||||
AddressOf(argvalue, argvalue.position),
|
||||
parent as Node)
|
||||
}
|
||||
}
|
||||
return replacements
|
||||
}
|
||||
}
|
@ -22,10 +22,10 @@ internal class AstChecker(private val program: Program,
|
||||
if(mainBlocks.size>1)
|
||||
errors.err("more than one 'main' block", mainBlocks[0].position)
|
||||
if(mainBlocks.isEmpty())
|
||||
errors.err("there is no 'main' block", program.position)
|
||||
errors.err("there is no 'main' block", program.modules.firstOrNull()?.position ?: program.position)
|
||||
|
||||
for(mainBlock in mainBlocks) {
|
||||
val startSub = mainBlock.subScopes()["start"] as? Subroutine
|
||||
val startSub = mainBlock.subScope("start") as? Subroutine
|
||||
if (startSub == null) {
|
||||
errors.err("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position)
|
||||
} else {
|
||||
@ -58,7 +58,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(irqBlocks.size>1)
|
||||
errors.err("more than one 'irq' block", irqBlocks[0].position)
|
||||
for(irqBlock in irqBlocks) {
|
||||
val irqSub = irqBlock.subScopes()["irq"] as? Subroutine
|
||||
val irqSub = irqBlock.subScope("irq") as? Subroutine
|
||||
if (irqSub != null) {
|
||||
if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
|
||||
errors.err("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position)
|
||||
@ -82,7 +82,7 @@ internal class AstChecker(private val program: Program,
|
||||
override fun visit(returnStmt: Return) {
|
||||
val expectedReturnValues = returnStmt.definingSubroutine()?.returntypes ?: emptyList()
|
||||
if(expectedReturnValues.size>1) {
|
||||
throw AstException("cannot use a return with one value in a subroutine that has multiple return values: $returnStmt")
|
||||
throw FatalAstException("cannot use a return with one value in a subroutine that has multiple return values: $returnStmt")
|
||||
}
|
||||
|
||||
if(expectedReturnValues.isEmpty() && returnStmt.value!=null) {
|
||||
@ -326,7 +326,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop) {
|
||||
if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y"))
|
||||
if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y")) // TODO use callgraph?
|
||||
errors.warn("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position)
|
||||
if(repeatLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
errors.err("condition value should be an integer type", repeatLoop.untilCondition.position)
|
||||
@ -334,7 +334,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(whileLoop: WhileLoop) {
|
||||
if(whileLoop.condition.referencesIdentifiers("A", "X", "Y"))
|
||||
if(whileLoop.condition.referencesIdentifiers("A", "X", "Y")) // TODO use callgraph?
|
||||
errors.warn("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position)
|
||||
if(whileLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
errors.err("condition value should be an integer type", whileLoop.condition.position)
|
||||
@ -423,9 +423,6 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
if (assignment is Assignment) {
|
||||
|
||||
if (assignment.aug_op != null)
|
||||
throw FatalAstException("augmented assignment should have been converted into normal assignment")
|
||||
|
||||
val targetDatatype = assignTarget.inferType(program, assignment)
|
||||
if (targetDatatype.isKnown) {
|
||||
val constVal = assignment.value.constValue(program)
|
||||
@ -466,6 +463,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
// TODO use callgraph for check?
|
||||
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
|
||||
err("recursive var declaration")
|
||||
}
|
||||
@ -588,7 +586,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
val declValue = decl.value
|
||||
if(declValue!=null && decl.type==VarDeclType.VAR && !declValue.inferType(program).istype(decl.datatype))
|
||||
throw FatalAstException("initialisation value $declValue is of different type (${declValue.inferType(program)} as the variable (${decl.datatype}) at ${decl.position}")
|
||||
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
|
||||
|
||||
super.visit(decl)
|
||||
}
|
||||
@ -848,7 +846,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(functionCallStatement.target.nameInSource.last() in setOf("lsl", "lsr", "rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
|
||||
// in-place modification, can't be done on literals
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is RegisterExpr && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
||||
errors.err("can't use that as argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
super.visit(functionCallStatement)
|
||||
@ -896,6 +894,8 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
} else if(target is Subroutine) {
|
||||
if(target.regXasResult())
|
||||
errors.warn("subroutine call return value in X register is discarded and replaced by 0", position)
|
||||
if(args.size!=target.parameters.size)
|
||||
errors.err("invalid number of arguments", position)
|
||||
else {
|
||||
@ -914,7 +914,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(target.isAsmSubroutine) {
|
||||
if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)) {
|
||||
if (arg.first.value !is NumericLiteralValue && arg.first.value !is IdentifierReference)
|
||||
errors.warn("calling a subroutine that expects X as a parameter is problematic, more so when providing complex arguments. If you see a compiler error/crash about this later, try to simplify this call", position)
|
||||
errors.warn("calling a subroutine that expects X as a parameter is problematic. If you see a compiler error/crash about this later, try to change this call", position)
|
||||
}
|
||||
|
||||
// check if the argument types match the register(pairs)
|
||||
@ -1268,7 +1268,7 @@ internal class AstChecker(private val program: Program,
|
||||
correct = array.all { it in -32768..32767 }
|
||||
}
|
||||
DataType.ARRAY_F -> correct = true
|
||||
else -> throw AstException("invalid array type $type")
|
||||
else -> throw FatalAstException("invalid array type $type")
|
||||
}
|
||||
if (!correct)
|
||||
errors.err("array value out of range for type $type", value.position)
|
||||
|
@ -10,7 +10,7 @@ import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
|
||||
// TODO implement using AstWalker instead of IAstModifyingVisitor
|
||||
internal class AstIdentifiersChecker(private val program: Program,
|
||||
private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
private var blocks = mutableMapOf<String, Block>()
|
||||
|
@ -41,6 +41,17 @@ interface IAstModification {
|
||||
}
|
||||
}
|
||||
|
||||
class InsertLast(val stmt: Statement, val parent: Node) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
parent.statements.add(stmt)
|
||||
stmt.linkParents(parent)
|
||||
} else {
|
||||
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
@ -56,7 +67,7 @@ interface IAstModification {
|
||||
class ReplaceNode(val node: Node, val replacement: Node, val parent: Node) : IAstModification {
|
||||
override fun perform() {
|
||||
parent.replaceChildNode(node, replacement)
|
||||
replacement.parent = parent
|
||||
replacement.linkParents(parent)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
// TODO replace all occurrences of this with AstWalker
|
||||
interface IAstModifyingVisitor {
|
||||
fun visit(program: Program) {
|
||||
program.modules.forEach { it.accept(this) }
|
||||
|
@ -1,195 +1,125 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
internal class StatementReorderer(private val program: Program): IAstModifyingVisitor {
|
||||
internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
// Reorders the statements in a way the compiler needs.
|
||||
// - 'main' block must be the very first statement UNLESS it has an address set.
|
||||
// - blocks are ordered by address, where blocks without address are put at the end.
|
||||
// - in every scope:
|
||||
// -- the directives '%output', '%launcher', '%zeropage', '%zpreserved', '%address' and '%option' will come first.
|
||||
// -- all vardecls then follow.
|
||||
// -- the remaining statements then follow in their original order.
|
||||
//
|
||||
// - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives.
|
||||
// - all other subroutines will be moved to the end of their block.
|
||||
// - library blocks are put last.
|
||||
// - blocks are ordered by address, where blocks without address are placed last.
|
||||
// - in every scope, most directives and vardecls are moved to the top.
|
||||
// - the 'start' subroutine is moved to the top.
|
||||
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
|
||||
// - (syntax desugaring) augmented assignment is turned into regular assignment.
|
||||
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
|
||||
// - sorts the choices in when statement.
|
||||
// - a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
|
||||
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||
|
||||
|
||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
|
||||
|
||||
private val addVardecls = mutableMapOf<INameScope, MutableList<VarDecl>>()
|
||||
|
||||
override fun visit(module: Module) {
|
||||
addVardecls.clear()
|
||||
super.visit(module)
|
||||
|
||||
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
|
||||
val (blocks, other) = module.statements.partition { it is Block }
|
||||
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
|
||||
|
||||
// make sure user-defined blocks come BEFORE library blocks, and move the "main" block to the top of everything
|
||||
val nonLibraryBlocks = module.statements.withIndex()
|
||||
.filter { it.value is Block && !(it.value as Block).isInLibrary }
|
||||
.map { it.index to it.value }
|
||||
.reversed()
|
||||
for(nonLibBlock in nonLibraryBlocks)
|
||||
module.statements.removeAt(nonLibBlock.first)
|
||||
for(nonLibBlock in nonLibraryBlocks)
|
||||
module.statements.add(0, nonLibBlock.second)
|
||||
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" }
|
||||
if(mainBlock!=null && (mainBlock as Block).address==null) {
|
||||
module.remove(mainBlock)
|
||||
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
|
||||
if(mainBlock!=null && mainBlock.address==null) {
|
||||
module.statements.remove(mainBlock)
|
||||
module.statements.add(0, mainBlock)
|
||||
}
|
||||
|
||||
val varDecls = module.statements.filterIsInstance<VarDecl>()
|
||||
module.statements.removeAll(varDecls)
|
||||
module.statements.addAll(0, varDecls)
|
||||
|
||||
val directives = module.statements.filter {it is Directive && it.directive in directivesToMove}
|
||||
module.statements.removeAll(directives)
|
||||
module.statements.addAll(0, directives)
|
||||
|
||||
for((where, decls) in addVardecls) {
|
||||
where.statements.addAll(0, decls)
|
||||
decls.forEach { it.linkParents(where as Node) }
|
||||
}
|
||||
reorderVardeclsAndDirectives(module.statements)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun visit(block: Block): Statement {
|
||||
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
|
||||
val varDecls = statements.filterIsInstance<VarDecl>()
|
||||
statements.removeAll(varDecls)
|
||||
statements.addAll(0, varDecls)
|
||||
|
||||
val subroutines = block.statements.filterIsInstance<Subroutine>()
|
||||
var numSubroutinesAtEnd = 0
|
||||
// move all subroutines to the end of the block
|
||||
for (subroutine in subroutines) {
|
||||
if(subroutine.name!="start" || block.name!="main") {
|
||||
block.remove(subroutine)
|
||||
block.statements.add(subroutine)
|
||||
}
|
||||
numSubroutinesAtEnd++
|
||||
val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
|
||||
statements.removeAll(directives)
|
||||
statements.addAll(0, directives)
|
||||
}
|
||||
|
||||
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
parent as Module
|
||||
if(block.isInLibrary) {
|
||||
return listOf(
|
||||
IAstModification.Remove(block, parent),
|
||||
IAstModification.InsertLast(block, parent)
|
||||
)
|
||||
}
|
||||
// move the "start" subroutine to the top
|
||||
if(block.name=="main") {
|
||||
block.statements.singleOrNull { it is Subroutine && it.name == "start" } ?.let {
|
||||
block.remove(it)
|
||||
block.statements.add(0, it)
|
||||
numSubroutinesAtEnd--
|
||||
|
||||
reorderVardeclsAndDirectives(block.statements)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
if(subroutine.name=="start" && parent is Block) {
|
||||
if(parent.statements.filterIsInstance<Subroutine>().first().name!="start") {
|
||||
return listOf(
|
||||
IAstModification.Remove(subroutine, parent),
|
||||
IAstModification.InsertFirst(subroutine, parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val varDecls = block.statements.filterIsInstance<VarDecl>()
|
||||
block.statements.removeAll(varDecls)
|
||||
block.statements.addAll(0, varDecls)
|
||||
val directives = block.statements.filter {it is Directive && it.directive in directivesToMove}
|
||||
block.statements.removeAll(directives)
|
||||
block.statements.addAll(0, directives)
|
||||
block.linkParents(block.parent)
|
||||
|
||||
return super.visit(block)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine): Statement {
|
||||
super.visit(subroutine)
|
||||
|
||||
val varDecls = subroutine.statements.filterIsInstance<VarDecl>()
|
||||
subroutine.statements.removeAll(varDecls)
|
||||
subroutine.statements.addAll(0, varDecls)
|
||||
val directives = subroutine.statements.filter {it is Directive && it.directive in directivesToMove}
|
||||
subroutine.statements.removeAll(directives)
|
||||
subroutine.statements.addAll(0, directives)
|
||||
|
||||
return subroutine
|
||||
}
|
||||
|
||||
|
||||
private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl {
|
||||
if(scope !in addVardecls)
|
||||
addVardecls[scope] = mutableListOf()
|
||||
val declList = addVardecls.getValue(scope)
|
||||
val existing = declList.singleOrNull { it.name==variable.name }
|
||||
return if(existing!=null) {
|
||||
existing
|
||||
} else {
|
||||
declList.add(variable)
|
||||
variable
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val declValue = decl.value
|
||||
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
val declConstValue = declValue.constValue(program)
|
||||
if(declConstValue==null) {
|
||||
// move the vardecl (without value) to the scope and replace this with a regular assignment
|
||||
decl.value = null
|
||||
val target = AssignTarget(null, IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, null, declValue, decl.position)
|
||||
assign.linkParents(decl.parent)
|
||||
decl.value = null
|
||||
addVarDecl(decl.definingScope(), decl)
|
||||
return assign
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(decl, assign, parent),
|
||||
IAstModification.InsertFirst(decl, decl.definingScope() as Node)
|
||||
)
|
||||
}
|
||||
}
|
||||
return super.visit(decl)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment): Statement {
|
||||
val assg = super.visit(assignment)
|
||||
if(assg !is Assignment)
|
||||
return assg
|
||||
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
||||
val choices = whenStatement.choiceValues(program).sortedBy {
|
||||
it.first?.first() ?: Int.MAX_VALUE
|
||||
}
|
||||
whenStatement.choices.clear()
|
||||
choices.mapTo(whenStatement.choices) { it.second }
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
// see if a typecast is needed to convert the value's type into the proper target type
|
||||
val valueItype = assg.value.inferType(program)
|
||||
val targetItype = assg.target.inferType(program, assg)
|
||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
if(assignment.aug_op!=null) {
|
||||
return listOf(IAstModification.ReplaceNode(assignment, assignment.asDesugaredNonaugmented(), parent))
|
||||
}
|
||||
|
||||
if(targetItype.isKnown && valueItype.isKnown) {
|
||||
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
||||
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
||||
|
||||
// struct assignments will be flattened (if it's not a struct literal)
|
||||
if (valuetype == DataType.STRUCT && targettype == DataType.STRUCT) {
|
||||
val assignments = if (assg.value is StructLiteralValue) {
|
||||
flattenStructAssignmentFromStructLiteral(assg, program) // 'structvar = { ..... } '
|
||||
} else {
|
||||
flattenStructAssignmentFromIdentifier(assg, program) // 'structvar1 = structvar2'
|
||||
}
|
||||
return if (assignments.isEmpty()) {
|
||||
// something went wrong (probably incompatible struct types)
|
||||
// we'll get an error later from the AstChecker
|
||||
assg
|
||||
} else {
|
||||
val scope = AnonymousScope(assignments.toMutableList(), assg.position)
|
||||
scope.linkParents(assg.parent)
|
||||
scope
|
||||
}
|
||||
val valueType = assignment.value.inferType(program)
|
||||
val targetType = assignment.target.inferType(program, assignment)
|
||||
if(valueType.istype(DataType.STRUCT) && targetType.istype(DataType.STRUCT)) {
|
||||
val assignments = if (assignment.value is StructLiteralValue) {
|
||||
flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = { ..... } '
|
||||
} else {
|
||||
flattenStructAssignmentFromIdentifier(assignment, program) // 'structvar1 = structvar2'
|
||||
}
|
||||
if(assignments.isNotEmpty()) {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, parent) }
|
||||
modifications.add(IAstModification.Remove(assignment, parent))
|
||||
return modifications
|
||||
}
|
||||
}
|
||||
|
||||
if(assg.aug_op!=null) {
|
||||
// transform augmented assg into normal assg so we have one case less to deal with later
|
||||
val newTarget: Expression =
|
||||
when {
|
||||
assg.target.register != null -> RegisterExpr(assg.target.register!!, assg.target.position)
|
||||
assg.target.identifier != null -> assg.target.identifier!!
|
||||
assg.target.arrayindexed != null -> assg.target.arrayindexed!!
|
||||
assg.target.memoryAddress != null -> DirectMemoryRead(assg.target.memoryAddress!!.addressExpression, assg.value.position)
|
||||
else -> throw FatalAstException("strange assg")
|
||||
}
|
||||
|
||||
val expression = BinaryExpression(newTarget, assg.aug_op.substringBeforeLast('='), assg.value, assg.position)
|
||||
expression.linkParents(assg.parent)
|
||||
val convertedAssignment = Assignment(assg.target, null, expression, assg.position)
|
||||
convertedAssignment.linkParents(assg.parent)
|
||||
return super.visit(convertedAssignment)
|
||||
}
|
||||
|
||||
return assg
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> {
|
||||
|
@ -4,9 +4,7 @@ import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.functions.BuiltinFunctions
|
||||
@ -45,10 +43,33 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
||||
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
||||
if (valuetype != targettype) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
assignment.value,
|
||||
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
||||
assignment))
|
||||
if (valuetype isAssignableTo targettype) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
assignment.value,
|
||||
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
||||
assignment))
|
||||
} else {
|
||||
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> =
|
||||
listOf(IAstModification.ReplaceNode(cvalue, cvalue.cast(targettype), cvalue.parent))
|
||||
val cvalue = assignment.value.constValue(program)
|
||||
if(cvalue!=null) {
|
||||
val number = cvalue.number.toDouble()
|
||||
// more complex comparisons if the type is different, but the constant value is compatible
|
||||
if (valuetype == DataType.BYTE && targettype == DataType.UBYTE) {
|
||||
if(number>0)
|
||||
return castLiteral(cvalue)
|
||||
} else if (valuetype == DataType.WORD && targettype == DataType.UWORD) {
|
||||
if(number>0)
|
||||
return castLiteral(cvalue)
|
||||
} else if (valuetype == DataType.UBYTE && targettype == DataType.BYTE) {
|
||||
if(number<0x80)
|
||||
return castLiteral(cvalue)
|
||||
} else if (valuetype == DataType.UWORD && targettype == DataType.WORD) {
|
||||
if(number<0x8000)
|
||||
return castLiteral(cvalue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
@ -77,6 +98,12 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
call.args[arg.second.index],
|
||||
TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position),
|
||||
call as Node))
|
||||
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
|
||||
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
|
||||
call as Node))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,9 @@ class BuiltinFunctionStatementPlaceholder(val name: String, override val positio
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {}
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
replacement.parent = this
|
||||
}
|
||||
override val expensiveToInline = false
|
||||
}
|
||||
|
||||
@ -72,6 +74,7 @@ class Block(override val name: String,
|
||||
require(replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@ -139,6 +142,7 @@ open class Return(var value: Expression?, override val position: Position) : Sta
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression)
|
||||
value = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@ -274,6 +278,7 @@ open class VarDecl(val type: VarDeclType,
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===value)
|
||||
value = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@ -324,6 +329,7 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===index)
|
||||
index = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -345,7 +351,7 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
|
||||
fun size() = (index as? NumericLiteralValue)?.number?.toInt()
|
||||
}
|
||||
|
||||
open class Assignment(var target: AssignTarget, val aug_op : String?, var value: Expression, override val position: Position) : Statement() {
|
||||
open class Assignment(var target: AssignTarget, var aug_op : String?, var value: Expression, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline
|
||||
get() = value !is NumericLiteralValue
|
||||
@ -362,6 +368,7 @@ open class Assignment(var target: AssignTarget, val aug_op : String?, var value:
|
||||
node===value -> value = replacement as Expression
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@ -371,6 +378,30 @@ open class Assignment(var target: AssignTarget, val aug_op : String?, var value:
|
||||
override fun toString(): String {
|
||||
return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)")
|
||||
}
|
||||
|
||||
fun asDesugaredNonaugmented(): Assignment {
|
||||
val augmented = aug_op ?: return this
|
||||
|
||||
val leftOperand: Expression =
|
||||
when {
|
||||
target.register != null -> RegisterExpr(target.register!!, target.position)
|
||||
target.identifier != null -> target.identifier!!
|
||||
target.arrayindexed != null -> target.arrayindexed!!
|
||||
target.memoryAddress != null -> DirectMemoryRead(target.memoryAddress!!.addressExpression, value.position)
|
||||
else -> throw FatalAstException("strange this")
|
||||
}
|
||||
|
||||
val assignment =
|
||||
if(augmented=="setvalue") {
|
||||
Assignment(target, null, value, position)
|
||||
} else {
|
||||
val expression = BinaryExpression(leftOperand, augmented.substringBeforeLast('='), value, position)
|
||||
Assignment(target, null, expression, position)
|
||||
}
|
||||
assignment.linkParents(parent)
|
||||
|
||||
return assignment
|
||||
}
|
||||
}
|
||||
|
||||
data class AssignTarget(val register: Register?,
|
||||
@ -393,6 +424,7 @@ data class AssignTarget(val register: Register?,
|
||||
node===arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@ -503,6 +535,7 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is AssignTarget && node===target)
|
||||
target = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@ -557,6 +590,7 @@ class FunctionCallStatement(override var target: IdentifierReference,
|
||||
val idx = args.indexOf(node)
|
||||
args[idx] = replacement as Expression
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@ -607,6 +641,7 @@ class AnonymousScope(override var statements: MutableList<Statement>,
|
||||
require(replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@ -670,6 +705,7 @@ class Subroutine(override val name: String,
|
||||
require(replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@ -680,6 +716,8 @@ class Subroutine(override val name: String,
|
||||
return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
|
||||
}
|
||||
|
||||
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||
|
||||
fun amountOfRtsInAsm(): Int = statements
|
||||
.asSequence()
|
||||
.filter { it is InlineAssembly }
|
||||
@ -724,6 +762,7 @@ class IfStatement(var condition: Expression,
|
||||
node===elsepart -> elsepart = replacement as AnonymousScope
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@ -752,6 +791,7 @@ class BranchStatement(var condition: BranchCondition,
|
||||
node===elsepart -> elsepart = replacement as AnonymousScope
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@ -782,6 +822,7 @@ class ForLoop(val loopRegister: Register?,
|
||||
node===body -> body = replacement as AnonymousScope
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@ -817,6 +858,7 @@ class WhileLoop(var condition: Expression,
|
||||
node===body -> body = replacement as AnonymousScope
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@ -836,6 +878,7 @@ class ForeverLoop(var body: AnonymousScope, override val position: Position) : S
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is AnonymousScope && node===body)
|
||||
body = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@ -861,6 +904,7 @@ class RepeatLoop(var body: AnonymousScope,
|
||||
node===body -> body = replacement as AnonymousScope
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
@ -887,6 +931,7 @@ class WhenStatement(var condition: Expression,
|
||||
val idx = choices.indexOf(node)
|
||||
choices[idx] = replacement as WhenChoice
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
fun choiceValues(program: Program): List<Pair<List<Int>?, WhenChoice>> {
|
||||
@ -925,6 +970,7 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is AnonymousScope && node===statements)
|
||||
statements = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
@ -953,6 +999,7 @@ class StructDecl(override val name: String,
|
||||
require(replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
val numberOfElements: Int
|
||||
@ -976,6 +1023,7 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===addressExpression)
|
||||
addressExpression = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
|
104
compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt
Normal file
104
compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt
Normal file
@ -0,0 +1,104 @@
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if (decl.value == null && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
// a numeric vardecl without an initial value is initialized with zero.
|
||||
decl.value = decl.zeroElementValue()
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val decls = scope.statements.filterIsInstance<VarDecl>()
|
||||
val sub = scope.definingSubroutine()
|
||||
if (sub != null) {
|
||||
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
|
||||
var conflicts = false
|
||||
decls.forEach {
|
||||
val existing = existingVariables[it.name]
|
||||
if (existing != null) {
|
||||
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
|
||||
conflicts = true
|
||||
}
|
||||
}
|
||||
if (!conflicts) {
|
||||
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
|
||||
return numericVarsWithValue.map {
|
||||
val initValue = it.value!! // assume here that value has always been set by now
|
||||
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
|
||||
val target = AssignTarget(null, IdentifierReference(listOf(it.name), it.position), null, null, it.position)
|
||||
val assign = Assignment(target, null, initValue, it.position)
|
||||
initValue.parent = assign
|
||||
IAstModification.InsertFirst(assign, scope)
|
||||
} + decls.map { IAstModification.ReplaceNode(it, NopStatement(it.position), scope) } +
|
||||
decls.map { IAstModification.InsertFirst(it, sub) } // move it up to the subroutine
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
|
||||
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
if (subroutine.asmAddress == null
|
||||
&& subroutine.statements.isNotEmpty()
|
||||
&& subroutine.amountOfRtsInAsm() == 0
|
||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||
&& subroutine.statements.last() !is Subroutine) {
|
||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||
}
|
||||
|
||||
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
|
||||
val outerScope = subroutine.definingScope()
|
||||
val outerStatements = outerScope.statements
|
||||
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
|
||||
if (subroutineStmtIdx > 0
|
||||
&& outerStatements[subroutineStmtIdx - 1] !is Jump
|
||||
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
|
||||
&& outerStatements[subroutineStmtIdx - 1] !is Return
|
||||
&& outerScope !is Block) {
|
||||
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope as Node)
|
||||
}
|
||||
|
||||
return mods
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
// see if we can remove superfluous typecasts (outside of expressions)
|
||||
// such as casting byte<->ubyte, word<->uword
|
||||
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of.
|
||||
val sourceDt = typecast.expression.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|
||||
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
|
||||
if(typecast.parent !is Expression) {
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
}
|
||||
}
|
||||
else if(sourceDt in PassByReferenceDatatypes) {
|
||||
if(typecast.type==DataType.UWORD) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
AddressOf(typecast.expression as IdentifierReference, typecast.position),
|
||||
parent
|
||||
))
|
||||
} else {
|
||||
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
|
||||
}
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.optimizer.UnusedCodeRemover
|
||||
import prog8.optimizer.constantFold
|
||||
import prog8.optimizer.optimizeStatements
|
||||
import prog8.optimizer.simplifyExpressions
|
||||
@ -41,11 +42,13 @@ fun compileProgram(filepath: Path,
|
||||
optimizeAst(programAst, errors)
|
||||
postprocessAst(programAst, errors, compilationOptions)
|
||||
|
||||
// printAst(programAst)
|
||||
// printAst(programAst) // TODO
|
||||
|
||||
if(writeAssembly)
|
||||
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
|
||||
}
|
||||
System.out.flush()
|
||||
System.err.flush()
|
||||
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
||||
return CompilationResult(true, programAst, programName, importedFiles)
|
||||
|
||||
@ -161,13 +164,20 @@ private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = programAst.simplifyExpressions()
|
||||
val optsDone2 = programAst.optimizeStatements(errors)
|
||||
programAst.constantFold(errors) // because simplified statements and expressions could give rise to more constants that can be folded away:
|
||||
errors.handle()
|
||||
if (optsDone1 + optsDone2 == 0)
|
||||
break
|
||||
}
|
||||
|
||||
val remover = UnusedCodeRemover()
|
||||
remover.visit(programAst)
|
||||
remover.applyModifications()
|
||||
}
|
||||
|
||||
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
|
||||
programAst.transformAssignments(errors)
|
||||
errors.handle()
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
programAst.removeNopsFlattenAnonScopes()
|
||||
@ -181,14 +191,19 @@ private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir:
|
||||
optimize: Boolean, compilerOptions: CompilationOptions): String {
|
||||
// asm generation directly from the Ast,
|
||||
val zeropage = CompilationTarget.machine.getZeropage(compilerOptions)
|
||||
programAst.prepareAsmVariablesAndReturns(errors)
|
||||
programAst.processAstBeforeAsmGeneration(errors)
|
||||
errors.handle()
|
||||
|
||||
// printAst(programAst) // TODO
|
||||
|
||||
val assembly = CompilationTarget.asmGenerator(
|
||||
programAst,
|
||||
errors,
|
||||
zeropage,
|
||||
compilerOptions,
|
||||
outputDir).compileToAssembly(optimize)
|
||||
assembly.assemble(compilerOptions)
|
||||
errors.handle()
|
||||
return assembly.name
|
||||
}
|
||||
|
||||
|
@ -1,80 +0,0 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
class AsmVariableAndReturnsPreparer(val program: Program, val errors: ErrorReporter): AstWalker() {
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.value==null && decl.type==VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
// a numeric vardecl without an initial value is initialized with zero.
|
||||
decl.value = decl.zeroElementValue()
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val decls = scope.statements.filterIsInstance<VarDecl>()
|
||||
val sub = scope.definingSubroutine()
|
||||
if(sub!=null) {
|
||||
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
|
||||
var conflicts = false
|
||||
decls.forEach {
|
||||
val existing = existingVariables[it.name]
|
||||
if (existing!=null) {
|
||||
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
|
||||
conflicts = true
|
||||
}
|
||||
}
|
||||
if(!conflicts) {
|
||||
val numericVarsWithValue = decls.filter { it.value!=null && it.datatype in NumericDatatypes }
|
||||
return numericVarsWithValue.map {
|
||||
val initValue = it.value!! // assume here that value has always been set by now
|
||||
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
|
||||
val target = AssignTarget(null, IdentifierReference(listOf(it.name), it.position), null, null, it.position)
|
||||
val assign = Assignment(target, null, initValue, it.position)
|
||||
IAstModification.InsertFirst(assign, scope)
|
||||
} +
|
||||
decls.map { IAstModification.ReplaceNode(it, NopStatement(it.position), scope) } +
|
||||
decls.map { IAstModification.InsertFirst(it, sub) } // move it up to the subroutine
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
|
||||
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
if(subroutine.asmAddress==null
|
||||
&& subroutine.statements.isNotEmpty()
|
||||
&& subroutine.amountOfRtsInAsm()==0
|
||||
&& subroutine.statements.lastOrNull {it !is VarDecl } !is Return
|
||||
&& subroutine.statements.last() !is Subroutine) {
|
||||
mods += IAstModification.InsertAfter(subroutine.statements.last(), returnStmt, subroutine)
|
||||
}
|
||||
|
||||
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
|
||||
val outerScope = subroutine.definingScope()
|
||||
val outerStatements = outerScope.statements
|
||||
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
|
||||
if(subroutineStmtIdx>0
|
||||
&& outerStatements[subroutineStmtIdx-1] !is Jump
|
||||
&& outerStatements[subroutineStmtIdx-1] !is Subroutine
|
||||
&& outerStatements[subroutineStmtIdx-1] !is Return
|
||||
&& outerScope !is Block) {
|
||||
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx-1], returnStmt, outerScope as Node)
|
||||
}
|
||||
|
||||
return mods
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.Zeropage
|
||||
import java.nio.file.Path
|
||||
@ -12,6 +13,6 @@ internal interface CompilationTarget {
|
||||
lateinit var machine: IMachineDefinition
|
||||
lateinit var encodeString: (str: String, altEncoding: Boolean) -> List<Short>
|
||||
lateinit var decodeString: (bytes: List<Short>, altEncoding: Boolean) -> String
|
||||
lateinit var asmGenerator: (Program, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator
|
||||
lateinit var asmGenerator: (Program, ErrorReporter, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import kotlin.math.absoluteValue
|
||||
|
||||
|
||||
internal class AsmGen(private val program: Program,
|
||||
private val errors: ErrorReporter,
|
||||
private val zeropage: Zeropage,
|
||||
private val options: CompilationOptions,
|
||||
private val outputDir: Path): IAssemblyGenerator {
|
||||
@ -38,7 +39,7 @@ internal class AsmGen(private val program: Program,
|
||||
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
|
||||
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
|
||||
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
|
||||
private val assignmentAsmGen = AssignmentAsmGen(program, this)
|
||||
private val assignmentAsmGen = AssignmentAsmGen(program, errors, this)
|
||||
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
|
||||
internal val loopEndLabels = ArrayDeque<String>()
|
||||
internal val loopContinueLabels = ArrayDeque<String>()
|
||||
@ -913,6 +914,7 @@ internal class AsmGen(private val program: Program,
|
||||
val indexName = asmIdentifierName(index)
|
||||
out(" lda $indexName")
|
||||
}
|
||||
// TODO optimize more cases
|
||||
else -> {
|
||||
expressionsAsmGen.translateExpression(index)
|
||||
out(" inx | lda $ESTACK_LO_HEX,x")
|
||||
@ -920,6 +922,28 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun translateArrayIndexIntoY(expr: ArrayIndexedExpression) {
|
||||
when (val index = expr.arrayspec.index) {
|
||||
is NumericLiteralValue -> throw AssemblyError("this should be optimized directly")
|
||||
is RegisterExpr -> {
|
||||
when (index.register) {
|
||||
Register.A -> out(" tay")
|
||||
Register.X -> out(" txa | tay")
|
||||
Register.Y -> {}
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val indexName = asmIdentifierName(index)
|
||||
out(" ldy $indexName")
|
||||
}
|
||||
// TODO optimize more cases, see translateArrayIndexIntoA
|
||||
else -> {
|
||||
expressionsAsmGen.translateExpression(index)
|
||||
out(" inx | ldy $ESTACK_LO_HEX,x")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun translateExpression(expression: Expression) =
|
||||
expressionsAsmGen.translateExpression(expression)
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -40,8 +40,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
if (builtinFunc != null) {
|
||||
asmgen.translateFunctioncallExpression(expression, builtinFunc)
|
||||
} else {
|
||||
asmgen.translateFunctionCall(expression)
|
||||
val sub = expression.target.targetSubroutine(program.namespace)!!
|
||||
asmgen.translateFunctionCall(expression)
|
||||
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
||||
for ((_, reg) in returns) {
|
||||
if (!reg.stack) {
|
||||
@ -51,7 +51,18 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
RegisterOrPair.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex")
|
||||
RegisterOrPair.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex")
|
||||
RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't push X register - use a variable")
|
||||
RegisterOrPair.X -> {
|
||||
// return value in X register has been discarded, just push a zero
|
||||
asmgen.out(" lda #0 | sta $ESTACK_LO_HEX,x | dex")
|
||||
}
|
||||
RegisterOrPair.AX -> {
|
||||
// return value in X register has been discarded, just push a zero in this place
|
||||
asmgen.out(" sta $ESTACK_LO_HEX,x | lda #0 | sta $ESTACK_HI_HEX,x | dex")
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
// return value in X register has been discarded, just push a zero in this place
|
||||
asmgen.out(" lda #0 | sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex")
|
||||
}
|
||||
}
|
||||
}
|
||||
// return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc
|
||||
@ -110,7 +121,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot case a pass-by-reference datatypes into something else")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
@ -320,8 +331,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
// the general, non-optimized cases
|
||||
translateExpression(expr.left)
|
||||
translateExpression(expr.right)
|
||||
if(leftDt!=rightDt)
|
||||
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical") // is this strictly required always?
|
||||
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|
||||
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
|
||||
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
|
||||
|
||||
when (leftDt) {
|
||||
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
|
||||
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
|
||||
|
@ -19,7 +19,8 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
// output the code to setup the parameters and perform the actual call
|
||||
// does NOT output the code to deal with the result values!
|
||||
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
if(Register.X in sub.asmClobbers)
|
||||
val saveX = Register.X in sub.asmClobbers || sub.regXasResult()
|
||||
if(saveX)
|
||||
asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y...
|
||||
|
||||
val subName = asmgen.asmIdentifierName(stmt.target)
|
||||
@ -30,7 +31,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
asmgen.out(" jsr $subName")
|
||||
|
||||
if(Register.X in sub.asmClobbers)
|
||||
if(saveX)
|
||||
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
|
||||
}
|
||||
|
||||
@ -42,7 +43,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
if(!argumentTypeCompatible(sourceDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
if(sub.asmParameterRegisters.isEmpty()) {
|
||||
// pass parameter via a variable
|
||||
// pass parameter via a regular variable (not via registers)
|
||||
val paramVar = parameter.value
|
||||
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
|
||||
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
|
||||
@ -54,7 +55,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
|
||||
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
|
||||
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble())
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh
|
||||
else -> throw AssemblyError("weird parameter datatype")
|
||||
}
|
||||
}
|
||||
@ -64,7 +65,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
|
||||
in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
|
||||
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh
|
||||
else -> throw AssemblyError("weird parameter datatype")
|
||||
}
|
||||
}
|
||||
@ -202,11 +203,20 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val sourceName = asmgen.asmIdentifierName(value)
|
||||
when (register) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
|
||||
else -> {}
|
||||
if(sourceDt in PassByReferenceDatatypes) {
|
||||
when (register) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
|
||||
else -> {}
|
||||
}
|
||||
} else {
|
||||
when (register) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@ -225,6 +235,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
|
||||
if(argType isAssignableTo paramType)
|
||||
return true
|
||||
if(argType in ByteDatatypes && paramType in ByteDatatypes)
|
||||
return true
|
||||
if(argType in WordDatatypes && paramType in WordDatatypes)
|
||||
return true
|
||||
|
||||
// we have a special rule for some types.
|
||||
// strings are assignable to UWORD, for example, and vice versa
|
||||
|
@ -104,17 +104,14 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
// TODO optimize common cases
|
||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
// TODO optimize common cases
|
||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||
}
|
||||
else -> {
|
||||
// TODO optimize common cases
|
||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||
}
|
||||
|
156
compiler/src/prog8/optimizer/AssignmentTransformer.kt
Normal file
156
compiler/src/prog8/optimizer/AssignmentTransformer.kt
Normal file
@ -0,0 +1,156 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.PostIncrDecr
|
||||
|
||||
|
||||
|
||||
internal class AssignmentTransformer(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||
|
||||
var optimizationsDone: Int = 0
|
||||
|
||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
// modify A = A + 5 back into augmented form A += 5 for easier code generation for optimized in-place assignments
|
||||
// also to put code generation stuff together, single value assignment (A = 5) is converted to a special
|
||||
// augmented form as wel (with the operator "setvalue")
|
||||
if (assignment.aug_op == null) {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null) {
|
||||
if (assignment.target.isSameAs(binExpr.left)) {
|
||||
assignment.value = binExpr.right
|
||||
assignment.aug_op = binExpr.operator + "="
|
||||
assignment.value.parent = assignment
|
||||
optimizationsDone++
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
assignment.aug_op = "setvalue"
|
||||
optimizationsDone++
|
||||
} else if(assignment.aug_op == "+=") {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null) {
|
||||
val leftnum = binExpr.left.constValue(program)?.number?.toDouble()
|
||||
val rightnum = binExpr.right.constValue(program)?.number?.toDouble()
|
||||
if(binExpr.operator == "+") {
|
||||
when {
|
||||
leftnum == 1.0 -> {
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
leftnum == 2.0 -> {
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
rightnum == 1.0 -> {
|
||||
// x += y + 1 -> x += y , x++
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
|
||||
)
|
||||
}
|
||||
rightnum == 2.0 -> {
|
||||
// x += y + 2 -> x += y , x++, x++
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if(binExpr.operator == "-") {
|
||||
when {
|
||||
leftnum == 1.0 -> {
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
leftnum == 2.0 -> {
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
rightnum == 1.0 -> {
|
||||
// x += y - 1 -> x += y , x--
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
|
||||
)
|
||||
}
|
||||
rightnum == 2.0 -> {
|
||||
// x += y - 2 -> x += y , x--, x--
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(assignment.aug_op == "-=") {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null) {
|
||||
val leftnum = binExpr.left.constValue(program)?.number?.toDouble()
|
||||
val rightnum = binExpr.right.constValue(program)?.number?.toDouble()
|
||||
if(binExpr.operator == "+") {
|
||||
when {
|
||||
leftnum == 1.0 -> {
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
leftnum == 2.0 -> {
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
rightnum == 1.0 -> {
|
||||
// x -= y + 1 -> x -= y , x--
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
|
||||
)
|
||||
}
|
||||
rightnum == 2.0 -> {
|
||||
// x -= y + 2 -> x -= y , x--, x--
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if(binExpr.operator == "-") {
|
||||
when {
|
||||
leftnum == 1.0 -> {
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
leftnum == 2.0 -> {
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
rightnum == 1.0 -> {
|
||||
// x -= y - 1 -> x -= y , x++
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
|
||||
)
|
||||
}
|
||||
rightnum == 2.0 -> {
|
||||
// x -= y - 2 -> x -= y , x++, x++
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent),
|
||||
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
}
|
@ -29,7 +29,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
|
||||
val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
||||
|
||||
// TODO add dataflow graph: what statements use what variables
|
||||
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
|
||||
val usedSymbols = mutableSetOf<Statement>()
|
||||
|
||||
init {
|
||||
@ -127,7 +127,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
if (decl.autogeneratedDontRemove || decl.definingModule().isLibraryModule) {
|
||||
// make sure autogenerated vardecls are in the used symbols
|
||||
// make sure autogenerated vardecls are in the used symbols and are never removed as 'unused'
|
||||
addNodeAndParentScopes(decl)
|
||||
}
|
||||
|
||||
|
@ -10,12 +10,13 @@ import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
|
||||
// TODO implement using AstWalker instead of IAstModifyingVisitor
|
||||
internal class ConstantFoldingOptimizer(private val program: Program, private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
var optimizationsDone: Int = 0
|
||||
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
// TODO: use call tree for this?
|
||||
// TODO: use call graph for this?
|
||||
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
|
||||
errors.err("recursive var declaration", decl.position)
|
||||
return decl
|
||||
|
@ -14,12 +14,6 @@ import kotlin.math.pow
|
||||
/*
|
||||
todo add more expression optimizations
|
||||
|
||||
x + x -> x << 1 (for words... for bytes too?)
|
||||
x + x + x + x -> x << 2 (for words... for bytes too?)
|
||||
x + x + x -> ???? x*3 ??? words/bytes?
|
||||
x - x -> 0
|
||||
|
||||
|
||||
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
|
||||
|
||||
*/
|
||||
@ -31,7 +25,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
if (assignment.aug_op != null)
|
||||
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
|
||||
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
@ -103,7 +97,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (!leftIDt.isKnown || !rightIDt.isKnown)
|
||||
throw FatalAstException("can't determine datatype of both expression operands $expr")
|
||||
|
||||
|
||||
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
|
||||
if (leftVal != null && expr.operator in associativeOperators && rightVal == null)
|
||||
return listOf(IAstModification.SwapOperands(expr))
|
||||
@ -180,6 +173,64 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator == ">=" && rightVal?.number == 0) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
// unsigned >= 0 --> true
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
|
||||
}
|
||||
when(leftDt) {
|
||||
DataType.BYTE -> {
|
||||
// signed >=0 --> signed ^ $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(expr.left, "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
// signedw >=0 --> msb(signedw) ^ $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
|
||||
mutableListOf(expr.left),
|
||||
expr.position
|
||||
), "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator == "<" && rightVal?.number == 0) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
// unsigned < 0 --> false
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent))
|
||||
}
|
||||
when(leftDt) {
|
||||
DataType.BYTE -> {
|
||||
// signed < 0 --> signed & $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(expr.left, "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
// signedw < 0 --> msb(signedw) & $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
|
||||
mutableListOf(expr.left),
|
||||
expr.position
|
||||
), "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
// simplify when a term is constant and directly determines the outcome
|
||||
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
|
||||
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
|
||||
@ -258,6 +309,14 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
}
|
||||
|
||||
private fun optimizeAdd(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
|
||||
if(expr.left.isSameAs(expr.right)) {
|
||||
// optimize X+X into X *2
|
||||
expr.operator = "*"
|
||||
expr.right = NumericLiteralValue.optimalInteger(2, expr.right.position)
|
||||
expr.right.linkParents(expr)
|
||||
return expr
|
||||
}
|
||||
|
||||
if (leftVal == null && rightVal == null)
|
||||
return null
|
||||
|
||||
@ -278,6 +337,11 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
}
|
||||
|
||||
private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
|
||||
if(expr.left.isSameAs(expr.right)) {
|
||||
// optimize X-X into 0
|
||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||
}
|
||||
|
||||
if (leftVal == null && rightVal == null)
|
||||
return null
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
@ -18,6 +18,7 @@ import kotlin.math.floor
|
||||
*/
|
||||
|
||||
|
||||
// TODO implement using AstWalker instead of IAstModifyingVisitor
|
||||
internal class StatementOptimizer(private val program: Program,
|
||||
private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
var optimizationsDone: Int = 0
|
||||
@ -28,7 +29,6 @@ internal class StatementOptimizer(private val program: Program,
|
||||
private val vardeclsToRemove = mutableListOf<VarDecl>()
|
||||
|
||||
override fun visit(program: Program) {
|
||||
removeUnusedCode(callgraph)
|
||||
super.visit(program)
|
||||
|
||||
for(decl in vardeclsToRemove) {
|
||||
@ -36,45 +36,6 @@ internal class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeUnusedCode(callgraph: CallGraph) {
|
||||
// remove all subroutines that aren't called, or are empty
|
||||
val removeSubroutines = mutableSetOf<Subroutine>()
|
||||
val entrypoint = program.entrypoint()
|
||||
program.modules.forEach {
|
||||
callgraph.forAllSubroutines(it) { sub ->
|
||||
if (sub !== entrypoint && !sub.keepAlways && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
|
||||
removeSubroutines.add(sub)
|
||||
}
|
||||
}
|
||||
|
||||
if (removeSubroutines.isNotEmpty()) {
|
||||
removeSubroutines.forEach {
|
||||
it.definingScope().remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
val removeBlocks = mutableSetOf<Block>()
|
||||
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
|
||||
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
|
||||
removeBlocks.add(block)
|
||||
}
|
||||
|
||||
if (removeBlocks.isNotEmpty()) {
|
||||
removeBlocks.forEach { it.definingScope().remove(it) }
|
||||
}
|
||||
|
||||
// remove modules that are not imported, or are empty (unless it's a library modules)
|
||||
val removeModules = mutableSetOf<Module>()
|
||||
program.modules.forEach {
|
||||
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
|
||||
removeModules.add(it)
|
||||
}
|
||||
|
||||
if (removeModules.isNotEmpty()) {
|
||||
program.modules.removeAll(removeModules)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(block: Block): Statement {
|
||||
if("force_output" !in block.options()) {
|
||||
if (block.containsNoCodeNorVars()) {
|
||||
@ -358,20 +319,19 @@ internal class StatementOptimizer(private val program: Program,
|
||||
|
||||
private fun hasContinueOrBreak(scope: INameScope): Boolean {
|
||||
|
||||
class Searcher: IAstModifyingVisitor
|
||||
class Searcher: IAstVisitor
|
||||
{
|
||||
var count=0
|
||||
|
||||
override fun visit(breakStmt: Break): Statement {
|
||||
override fun visit(breakStmt: Break) {
|
||||
count++
|
||||
return super.visit(breakStmt)
|
||||
}
|
||||
|
||||
override fun visit(contStmt: Continue): Statement {
|
||||
override fun visit(contStmt: Continue) {
|
||||
count++
|
||||
return super.visit(contStmt)
|
||||
}
|
||||
}
|
||||
|
||||
val s=Searcher()
|
||||
for(stmt in scope.statements) {
|
||||
stmt.accept(s)
|
||||
@ -407,7 +367,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
|
||||
override fun visit(assignment: Assignment): Statement {
|
||||
if(assignment.aug_op!=null)
|
||||
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
|
||||
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
|
||||
|
||||
if(assignment.target isSameAs assignment.value) {
|
||||
if(assignment.target.isNotMemory(program.namespace)) {
|
||||
|
38
compiler/src/prog8/optimizer/UnusedCodeRemover.kt
Normal file
38
compiler/src/prog8/optimizer/UnusedCodeRemover.kt
Normal file
@ -0,0 +1,38 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.Block
|
||||
|
||||
|
||||
internal class UnusedCodeRemover: AstWalker() {
|
||||
|
||||
override fun before(program: Program, parent: Node): Iterable<IAstModification> {
|
||||
val callgraph = CallGraph(program)
|
||||
val removals = mutableListOf<IAstModification>()
|
||||
|
||||
// remove all subroutines that aren't called, or are empty
|
||||
val entrypoint = program.entrypoint()
|
||||
program.modules.forEach {
|
||||
callgraph.forAllSubroutines(it) { sub ->
|
||||
if (sub !== entrypoint && !sub.keepAlways && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
|
||||
removals.add(IAstModification.Remove(sub, sub.definingScope() as Node))
|
||||
}
|
||||
}
|
||||
|
||||
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
|
||||
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
|
||||
removals.add(IAstModification.Remove(block, block.definingScope() as Node))
|
||||
}
|
||||
|
||||
// remove modules that are not imported, or are empty (unless it's a library modules)
|
||||
program.modules.forEach {
|
||||
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
|
||||
removals.add(IAstModification.Remove(it, it.parent)) // TODO does removing modules work like this?
|
||||
}
|
||||
|
||||
return removals
|
||||
}
|
||||
}
|
@ -2,27 +2,11 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
- implement the asm for bitshift on arrays (last missing assembly code generation)
|
||||
- finalize (most) of the still missing "new" assignment asm code generation
|
||||
|
||||
- aliases for imported symbols for example perhaps '%alias print = c64scr.print'
|
||||
- option to load library files from a directory instead of the embedded ones (easier library development/debugging)
|
||||
|
||||
|
||||
|
||||
|
||||
Memory Block Operations integrated in language?
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
array/string memory block operations?
|
||||
|
||||
- array operations
|
||||
copy (from another array with the same length), shift-N(left,right), rotate-N(left,right)
|
||||
clear (set whole array to the given value, default 0)
|
||||
|
||||
- array operations ofcourse work identical on vars and on memory mapped vars of these types.
|
||||
|
||||
- strings: identical operations as on array.
|
||||
|
||||
For now, we have the ``memcopy`` and ``memset`` builtin functions.
|
||||
- investigate support for 8bitguy's Commander X16 platform https://murray2.com/forums/commander-x16.9/ and https://github.com/commanderx16/x16-docs
|
||||
|
||||
|
||||
More optimizations
|
||||
@ -30,6 +14,8 @@ More optimizations
|
||||
|
||||
Add more compiler optimizations to the existing ones.
|
||||
|
||||
- more targeted optimizations for assigment asm code, such as the following:
|
||||
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise.
|
||||
- remove unreachable code after an exit(), return or goto
|
||||
- working subroutine inlining (start with trivial routines, grow to taking care of vars and identifier refs to them)
|
||||
- add a compiler option to not include variable initialization code (useful if the program is expected to run only once, such as a game)
|
||||
@ -45,13 +31,13 @@ Eval stack redesign? (lot of work)
|
||||
|
||||
The eval stack is now a split lsb/msb stack using X as the stackpointer.
|
||||
Is it easier/faster to just use a single page unsplit stack?
|
||||
It could then even be moved into the zeropage to greatly reduce code size and slowness.
|
||||
It could then even be moved into the zeropage to reduce code size and slowness.
|
||||
|
||||
Or just move the LSB portion into a slab of the zeropage.
|
||||
|
||||
Allocate a fixed word in ZP that is the Top Of Stack value so we can always operate on TOS directly
|
||||
without having to index with X into the eval stack all the time?
|
||||
This could GREATLY improvde code size and speed for operatios that work on just a single value.
|
||||
This could GREATLY improve code size and speed for operations that work on just a single value.
|
||||
|
||||
|
||||
Bug Fixing
|
||||
@ -64,4 +50,3 @@ Misc
|
||||
|
||||
Several ideas were discussed on my reddit post
|
||||
https://www.reddit.com/r/programming/comments/alhj59/creating_a_programming_language_and_cross/
|
||||
|
||||
|
@ -106,7 +106,7 @@ main {
|
||||
|
||||
check_eval_stack()
|
||||
|
||||
c64scr.print("\nyou should see no errors above.")
|
||||
c64scr.print("\nyou should see no errors printed above (only at first run).")
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,922 +0,0 @@
|
||||
%import c64utils
|
||||
;%import c64flt
|
||||
;%option enable_floats
|
||||
%zeropage dontuse
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
c64scr.print("ubyte shift left\n")
|
||||
A = shiftlb0()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb1()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb2()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb3()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb4()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb5()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb6()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb7()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb8()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb9()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print("enter to continue:\n")
|
||||
void c64.CHRIN()
|
||||
|
||||
c64scr.print("ubyte shift right\n")
|
||||
A = shiftrb0()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb1()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb2()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb3()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb4()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb5()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb6()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb7()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb8()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb9()
|
||||
c64scr.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print("enter to continue:\n")
|
||||
void c64.CHRIN()
|
||||
|
||||
|
||||
|
||||
|
||||
c64scr.print("signed byte shift left\n")
|
||||
byte signedb
|
||||
signedb = shiftlsb0()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb1()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb2()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb3()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb4()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb5()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb6()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb7()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb8()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb9()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print("enter to continue:\n")
|
||||
void c64.CHRIN()
|
||||
|
||||
c64scr.print("signed byte shift right\n")
|
||||
signedb = shiftrsb0()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb1()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb2()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb3()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb4()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb5()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb6()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb7()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb8()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb9()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print("enter to continue:\n")
|
||||
void c64.CHRIN()
|
||||
|
||||
|
||||
|
||||
|
||||
c64scr.print("uword shift left\n")
|
||||
uword uw
|
||||
uw = shiftluw0()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw1()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw2()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw3()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw4()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw5()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw6()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw7()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw8()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw9()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw10()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw11()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw12()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw13()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw14()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw15()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw16()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw17()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print("enter to continue:\n")
|
||||
void c64.CHRIN()
|
||||
|
||||
c64scr.print("uword shift right\n")
|
||||
uw = shiftruw0()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw1()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw2()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw3()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw4()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw5()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw6()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw7()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw8()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw9()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw10()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw11()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw12()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw13()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw14()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw15()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw16()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw17()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print("enter to continue:\n")
|
||||
void c64.CHRIN()
|
||||
|
||||
c64scr.print("signed word shift left\n")
|
||||
word sw
|
||||
sw = shiftlsw0()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw1()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw2()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw3()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw4()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw5()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw6()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw7()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw8()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw9()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw10()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw11()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw12()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw13()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw14()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw15()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw16()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw17()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print("enter to continue:\n")
|
||||
void c64.CHRIN()
|
||||
|
||||
c64scr.print("signed word shift right\n")
|
||||
sw = shiftrsw0()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw1()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw2()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw3()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw4()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw5()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw6()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw7()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw8()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw9()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw10()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw11()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw12()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw13()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw14()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw15()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw16()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw17()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
|
||||
}
|
||||
|
||||
sub shiftruw0() -> uword {
|
||||
uword q = $a49f
|
||||
return q >> 0
|
||||
}
|
||||
|
||||
sub shiftruw1() -> uword {
|
||||
uword q = $a49f
|
||||
return q >> 1
|
||||
}
|
||||
|
||||
sub shiftruw2() -> uword {
|
||||
uword q = $a49f
|
||||
return q >> 2
|
||||
}
|
||||
|
||||
sub shiftruw3() -> uword {
|
||||
uword q = $a49f
|
||||
return q >> 3
|
||||
}
|
||||
|
||||
sub shiftruw4() -> uword {
|
||||
uword q = $a49f
|
||||
return q >> 4
|
||||
}
|
||||
|
||||
sub shiftruw5() -> uword {
|
||||
uword q = $a49f
|
||||
return q >> 5
|
||||
}
|
||||
|
||||
sub shiftruw6() -> uword {
|
||||
uword q = $a49f
|
||||
return q >> 6
|
||||
}
|
||||
|
||||
sub shiftruw7() -> uword {
|
||||
uword q = $a49f
|
||||
return q >> 7
|
||||
}
|
||||
|
||||
sub shiftruw8() -> uword {
|
||||
uword q = $a49f
|
||||
return (q >> 8)
|
||||
}
|
||||
|
||||
sub shiftruw9() -> uword {
|
||||
uword q = $a49f
|
||||
return (q >> 9)
|
||||
}
|
||||
|
||||
sub shiftruw10() -> uword {
|
||||
uword q = $a49f
|
||||
return (q >> 10)
|
||||
}
|
||||
|
||||
sub shiftruw11() -> uword {
|
||||
uword q = $a49f
|
||||
return (q >> 11)
|
||||
}
|
||||
|
||||
sub shiftruw12() -> uword {
|
||||
uword q = $a49f
|
||||
return (q >> 12)
|
||||
}
|
||||
|
||||
sub shiftruw13() -> uword {
|
||||
uword q = $a49f
|
||||
return (q >> 13)
|
||||
}
|
||||
|
||||
sub shiftruw14() -> uword {
|
||||
uword q = $a49f
|
||||
return (q >> 14)
|
||||
}
|
||||
|
||||
sub shiftruw15() -> uword {
|
||||
uword q = $a49f
|
||||
return (q >> 15)
|
||||
}
|
||||
|
||||
sub shiftruw16() -> uword {
|
||||
uword q = $a49f
|
||||
return (q >> 16)
|
||||
}
|
||||
|
||||
sub shiftruw17() -> uword {
|
||||
uword q = $a49f
|
||||
return (q >> 17)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
sub shiftrsw0() -> word {
|
||||
word q = -12345
|
||||
return q >> 0
|
||||
}
|
||||
|
||||
sub shiftrsw1() -> word {
|
||||
word q = -12345
|
||||
return q >> 1
|
||||
}
|
||||
|
||||
sub shiftrsw2() -> word {
|
||||
word q = -12345
|
||||
return q >> 2
|
||||
}
|
||||
|
||||
sub shiftrsw3() -> word {
|
||||
word q = -12345
|
||||
return q >> 3
|
||||
}
|
||||
|
||||
sub shiftrsw4() -> word {
|
||||
word q = -12345
|
||||
return q >> 4
|
||||
}
|
||||
|
||||
sub shiftrsw5() -> word {
|
||||
word q = -12345
|
||||
return q >> 5
|
||||
}
|
||||
|
||||
sub shiftrsw6() -> word {
|
||||
word q = -12345
|
||||
return q >> 6
|
||||
}
|
||||
|
||||
sub shiftrsw7() -> word {
|
||||
word q = -12345
|
||||
return q >> 7
|
||||
}
|
||||
|
||||
sub shiftrsw8() -> word {
|
||||
word q = -12345
|
||||
return (q >> 8)
|
||||
}
|
||||
|
||||
sub shiftrsw9() -> word {
|
||||
word q = -12345
|
||||
return (q >> 9)
|
||||
}
|
||||
|
||||
sub shiftrsw10() -> word {
|
||||
word q = -12345
|
||||
return (q >> 10)
|
||||
}
|
||||
|
||||
sub shiftrsw11() -> word {
|
||||
word q = -12345
|
||||
return (q >> 11)
|
||||
}
|
||||
|
||||
sub shiftrsw12() -> word {
|
||||
word q = -12345
|
||||
return (q >> 12)
|
||||
}
|
||||
|
||||
sub shiftrsw13() -> word {
|
||||
word q = -12345
|
||||
return (q >> 13)
|
||||
}
|
||||
|
||||
sub shiftrsw14() -> word {
|
||||
word q = -12345
|
||||
return (q >> 14)
|
||||
}
|
||||
|
||||
sub shiftrsw15() -> word {
|
||||
word q = -12345
|
||||
return (q >> 15)
|
||||
}
|
||||
|
||||
sub shiftrsw16() -> word {
|
||||
word q = -12345
|
||||
return (q >> 16)
|
||||
}
|
||||
|
||||
sub shiftrsw17() -> word {
|
||||
word q = -12345
|
||||
return (q >> 17)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
sub shiftluw0() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 0
|
||||
}
|
||||
|
||||
sub shiftluw1() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 1
|
||||
}
|
||||
|
||||
sub shiftluw2() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 2
|
||||
}
|
||||
|
||||
sub shiftluw3() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 3
|
||||
}
|
||||
|
||||
sub shiftluw4() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 4
|
||||
}
|
||||
|
||||
sub shiftluw5() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 5
|
||||
}
|
||||
|
||||
sub shiftluw6() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 6
|
||||
}
|
||||
|
||||
sub shiftluw7() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 7
|
||||
}
|
||||
|
||||
sub shiftluw8() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 8
|
||||
}
|
||||
|
||||
sub shiftluw9() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 9
|
||||
}
|
||||
|
||||
sub shiftluw10() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 10
|
||||
}
|
||||
|
||||
sub shiftluw11() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 11
|
||||
}
|
||||
|
||||
sub shiftluw12() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 12
|
||||
}
|
||||
|
||||
sub shiftluw13() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 13
|
||||
}
|
||||
|
||||
sub shiftluw14() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 14
|
||||
}
|
||||
|
||||
sub shiftluw15() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 15
|
||||
}
|
||||
|
||||
sub shiftluw16() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 16
|
||||
}
|
||||
|
||||
sub shiftluw17() -> uword {
|
||||
uword q = $a49f
|
||||
return q << 17
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub shiftlsw0() -> word {
|
||||
word q = -12345
|
||||
return q << 0
|
||||
}
|
||||
|
||||
sub shiftlsw1() -> word {
|
||||
word q = -12345
|
||||
return q << 1
|
||||
}
|
||||
|
||||
sub shiftlsw2() -> word {
|
||||
word q = -12345
|
||||
return q << 2
|
||||
}
|
||||
|
||||
sub shiftlsw3() -> word {
|
||||
word q = -12345
|
||||
return q << 3
|
||||
}
|
||||
|
||||
sub shiftlsw4() -> word {
|
||||
word q = -12345
|
||||
return q << 4
|
||||
}
|
||||
|
||||
sub shiftlsw5() -> word {
|
||||
word q = -12345
|
||||
return q << 5
|
||||
}
|
||||
|
||||
sub shiftlsw6() -> word {
|
||||
word q = -12345
|
||||
return q << 6
|
||||
}
|
||||
|
||||
sub shiftlsw7() -> word {
|
||||
word q = -12345
|
||||
return q << 7
|
||||
}
|
||||
|
||||
sub shiftlsw8() -> word {
|
||||
word q = -12345
|
||||
return q << 8
|
||||
}
|
||||
|
||||
sub shiftlsw9() -> word {
|
||||
word q = -12345
|
||||
return q << 9
|
||||
}
|
||||
|
||||
sub shiftlsw10() -> word {
|
||||
word q = -12345
|
||||
return q << 10
|
||||
}
|
||||
|
||||
sub shiftlsw11() -> word {
|
||||
word q = -12345
|
||||
return q << 11
|
||||
}
|
||||
|
||||
sub shiftlsw12() -> word {
|
||||
word q = -12345
|
||||
return q << 12
|
||||
}
|
||||
|
||||
sub shiftlsw13() -> word {
|
||||
word q = -12345
|
||||
return q << 13
|
||||
}
|
||||
|
||||
sub shiftlsw14() -> word {
|
||||
word q = -12345
|
||||
return q << 14
|
||||
}
|
||||
|
||||
sub shiftlsw15() -> word {
|
||||
word q = -12345
|
||||
return q << 15
|
||||
}
|
||||
|
||||
sub shiftlsw16() -> word {
|
||||
word q = -12345
|
||||
return q << 16
|
||||
}
|
||||
|
||||
sub shiftlsw17() -> word {
|
||||
word q = -12345
|
||||
return q << 17
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub shiftlb0() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy << 0
|
||||
}
|
||||
sub shiftlb1() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy << 1
|
||||
}
|
||||
sub shiftlb2() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy << 2
|
||||
}
|
||||
sub shiftlb3() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy << 3
|
||||
}
|
||||
sub shiftlb4() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy << 4
|
||||
}
|
||||
sub shiftlb5() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy << 5
|
||||
}
|
||||
sub shiftlb6() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy << 6
|
||||
}
|
||||
sub shiftlb7() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy << 7
|
||||
}
|
||||
sub shiftlb8() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy << 8
|
||||
}
|
||||
sub shiftlb9() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy << 9
|
||||
}
|
||||
|
||||
sub shiftrb0() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy >> 0
|
||||
}
|
||||
sub shiftrb1() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy >> 1
|
||||
}
|
||||
sub shiftrb2() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy >> 2
|
||||
}
|
||||
sub shiftrb3() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy >> 3
|
||||
}
|
||||
sub shiftrb4() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy >> 4
|
||||
}
|
||||
sub shiftrb5() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy >> 5
|
||||
}
|
||||
sub shiftrb6() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy >> 6
|
||||
}
|
||||
sub shiftrb7() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy >> 7
|
||||
}
|
||||
sub shiftrb8() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy >> 8
|
||||
}
|
||||
sub shiftrb9() -> ubyte {
|
||||
ubyte yy=$ed
|
||||
return yy >> 9
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub shiftlsb0() -> byte {
|
||||
byte yy=-123
|
||||
return yy << 0
|
||||
}
|
||||
sub shiftlsb1() -> byte {
|
||||
byte yy=-123
|
||||
return yy << 1
|
||||
}
|
||||
sub shiftlsb2() -> byte {
|
||||
byte yy=-123
|
||||
return yy << 2
|
||||
}
|
||||
sub shiftlsb3() -> byte {
|
||||
byte yy=-123
|
||||
return yy << 3
|
||||
}
|
||||
sub shiftlsb4() -> byte {
|
||||
byte yy=-123
|
||||
return yy << 4
|
||||
}
|
||||
sub shiftlsb5() -> byte {
|
||||
byte yy=-123
|
||||
return yy << 5
|
||||
}
|
||||
sub shiftlsb6() -> byte {
|
||||
byte yy=-123
|
||||
return yy << 6
|
||||
}
|
||||
sub shiftlsb7() -> byte {
|
||||
byte yy=-123
|
||||
return yy << 7
|
||||
}
|
||||
sub shiftlsb8() -> byte {
|
||||
byte yy=-123
|
||||
return yy << 8
|
||||
}
|
||||
sub shiftlsb9() -> byte {
|
||||
byte yy=-123
|
||||
return yy << 9
|
||||
}
|
||||
|
||||
sub shiftrsb0() -> byte {
|
||||
byte yy=-123
|
||||
return yy >> 0
|
||||
}
|
||||
sub shiftrsb1() -> byte {
|
||||
byte yy=-123
|
||||
return yy >> 1
|
||||
}
|
||||
sub shiftrsb2() -> byte {
|
||||
byte yy=-123
|
||||
return yy >> 2
|
||||
}
|
||||
sub shiftrsb3() -> byte {
|
||||
byte yy=-123
|
||||
return yy >> 3
|
||||
}
|
||||
sub shiftrsb4() -> byte {
|
||||
byte yy=-123
|
||||
return yy >> 4
|
||||
}
|
||||
sub shiftrsb5() -> byte {
|
||||
byte yy=-123
|
||||
return yy >> 5
|
||||
}
|
||||
sub shiftrsb6() -> byte {
|
||||
byte yy=-123
|
||||
return yy >> 6
|
||||
}
|
||||
sub shiftrsb7() -> byte {
|
||||
byte yy=-123
|
||||
return yy >> 7
|
||||
}
|
||||
sub shiftrsb8() -> byte {
|
||||
byte yy=-123
|
||||
return yy >> 8
|
||||
}
|
||||
sub shiftrsb9() -> byte {
|
||||
byte yy=-123
|
||||
return yy >> 9
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ main {
|
||||
c64.SPXY[0] = 80
|
||||
c64.SPXY[1] = 100
|
||||
|
||||
c64.SCROLX = c64.SCROLX & %11110111 ; 38 column mode
|
||||
c64.SCROLX &= %11110111 ; 38 column mode
|
||||
|
||||
c64utils.set_rasterirq(1) ; enable animation
|
||||
|
||||
|
@ -17,67 +17,64 @@ graphics {
|
||||
c64scr.clear_screen($10, 0) ; pixel color $1 (white) backround $0 (black)
|
||||
}
|
||||
|
||||
|
||||
sub line(uword x1, ubyte y1, uword x2, ubyte y2) {
|
||||
; Bresenham algorithm
|
||||
word dx
|
||||
word dy
|
||||
byte ix = 1
|
||||
byte iy = 1
|
||||
if x2>x1 {
|
||||
dx = x2-x1
|
||||
} else {
|
||||
ix = -1
|
||||
dx = x1-x2
|
||||
; Bresenham algorithm.
|
||||
; This code special cases various quadrant loops to allow simple ++ and -- operations.
|
||||
if y1>y2 {
|
||||
; make sure dy is always positive to avoid 8 instead of just 4 special cases
|
||||
swap(x1, x2)
|
||||
swap(y1, y2)
|
||||
}
|
||||
if y2>y1 {
|
||||
dy = y2-y1
|
||||
} else {
|
||||
iy = -1
|
||||
dy = y1-y2
|
||||
}
|
||||
word dx2 = 2 * dx
|
||||
word dy2 = 2 * dy
|
||||
word d = 0
|
||||
ubyte positive_ix = true
|
||||
word dx = x2 - x1 as word
|
||||
word dy = y2 as word - y1 as word
|
||||
if dx < 0 {
|
||||
dx = -dx
|
||||
positive_ix = false
|
||||
}
|
||||
dx *= 2
|
||||
dy *= 2
|
||||
plotx = x1
|
||||
|
||||
if dx >= dy {
|
||||
if ix<0 {
|
||||
if positive_ix {
|
||||
forever {
|
||||
graphics.plot(y1)
|
||||
plot(y1)
|
||||
if plotx==x2
|
||||
return
|
||||
plotx--
|
||||
d += dy2
|
||||
plotx++
|
||||
d += dy
|
||||
if d > dx {
|
||||
y1 += iy
|
||||
d -= dx2
|
||||
y1++
|
||||
d -= dx
|
||||
}
|
||||
}
|
||||
} else {
|
||||
forever {
|
||||
graphics.plot(y1)
|
||||
plot(y1)
|
||||
if plotx==x2
|
||||
return
|
||||
plotx++
|
||||
d += dy2
|
||||
plotx--
|
||||
d += dy
|
||||
if d > dx {
|
||||
y1 += iy
|
||||
d -= dx2
|
||||
y1++
|
||||
d -= dx
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if iy<0 {
|
||||
}
|
||||
else {
|
||||
if positive_ix {
|
||||
forever {
|
||||
plot(y1)
|
||||
if y1 == y2
|
||||
return
|
||||
y1--
|
||||
d += dx2
|
||||
y1++
|
||||
d += dx
|
||||
if d > dy {
|
||||
plotx += ix as word
|
||||
d -= dy2
|
||||
plotx++
|
||||
d -= dy
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -86,10 +83,10 @@ graphics {
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx2
|
||||
d += dx
|
||||
if d > dy {
|
||||
plotx += ix as word
|
||||
d -= dy2
|
||||
plotx--
|
||||
d -= dy
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -101,7 +98,7 @@ graphics {
|
||||
ubyte ploty
|
||||
ubyte xx = radius
|
||||
ubyte yy = 0
|
||||
byte decisionOver2 = 1-xx
|
||||
byte decisionOver2 = 1-xx as byte
|
||||
|
||||
while xx>=yy {
|
||||
plotx = xcenter + xx
|
||||
@ -138,24 +135,29 @@ graphics {
|
||||
; Midpoint algorithm, filled
|
||||
ubyte xx = radius
|
||||
ubyte yy = 0
|
||||
byte decisionOver2 = 1-xx
|
||||
byte decisionOver2 = 1-xx as byte
|
||||
|
||||
while xx>=yy {
|
||||
ubyte cy_plus_yy = cy + yy
|
||||
ubyte cy_min_yy = cy - yy
|
||||
ubyte cy_plus_xx = cy + xx
|
||||
ubyte cy_min_xx = cy - xx
|
||||
|
||||
for plotx in cx to cx+xx {
|
||||
plot(cy + yy)
|
||||
plot(cy - yy)
|
||||
plot(cy_plus_yy)
|
||||
plot(cy_min_yy)
|
||||
}
|
||||
for plotx in cx-xx to cx-1 {
|
||||
plot(cy + yy)
|
||||
plot(cy - yy)
|
||||
plot(cy_plus_yy)
|
||||
plot(cy_min_yy)
|
||||
}
|
||||
for plotx in cx to cx+yy {
|
||||
plot(cy + xx)
|
||||
plot(cy - xx)
|
||||
plot(cy_plus_xx)
|
||||
plot(cy_min_xx)
|
||||
}
|
||||
for plotx in cx-yy to cx {
|
||||
plot(cy + xx)
|
||||
plot(cy - xx)
|
||||
plot(cy_plus_xx)
|
||||
plot(cy_min_xx)
|
||||
}
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
@ -175,7 +177,7 @@ graphics {
|
||||
; @(addr) |= ormask[lsb(px) & 7]
|
||||
; }
|
||||
|
||||
uword plotx ; 0..319
|
||||
uword plotx ; 0..319 ; separate 'parameter' for plot()
|
||||
|
||||
asmsub plot(ubyte ploty @A) { ; plotx is 16 bits 0 to 319... doesn't fit in a register
|
||||
%asm {{
|
||||
@ -212,7 +214,7 @@ _ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
|
||||
|
||||
; note: this can be even faster if we also have a 256 byte x-lookup table, but hey.
|
||||
; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
|
||||
; the y lookup tables encode this formula: bitmap_address + 320*(py>>3) + (py & 7) (y from 0..199)
|
||||
; the y lookup tables encodes this formula: bitmap_address + 320*(py>>3) + (py & 7) (y from 0..199)
|
||||
_y_lookup_hi
|
||||
.byte $20, $20, $20, $20, $20, $20, $20, $20, $21, $21, $21, $21, $21, $21, $21, $21
|
||||
.byte $22, $22, $22, $22, $22, $22, $22, $22, $23, $23, $23, $23, $23, $23, $23, $23
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,7 +1,5 @@
|
||||
%import c64lib
|
||||
%import c64graphics
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
main {
|
||||
|
||||
|
@ -71,8 +71,8 @@ main {
|
||||
ubyte dy = abs(y2 - y1)
|
||||
ubyte dx2 = 2 * dx
|
||||
ubyte dy2 = 2 * dy
|
||||
byte ix = sgn(x2 as byte - x1 as byte)
|
||||
byte iy = sgn(y2 as byte - y1 as byte)
|
||||
ubyte ix = sgn(x2 as byte - x1 as byte) as ubyte
|
||||
ubyte iy = sgn(y2 as byte - y1 as byte) as ubyte
|
||||
ubyte x = x1
|
||||
ubyte y = y1
|
||||
|
||||
@ -107,7 +107,7 @@ main {
|
||||
; Midpoint algorithm
|
||||
ubyte x = radius
|
||||
ubyte y = 0
|
||||
byte decisionOver2 = 1-x
|
||||
byte decisionOver2 = 1-x as byte
|
||||
|
||||
while x>=y {
|
||||
c64scr.setcc(xcenter + x, ycenter + y as ubyte, 81, 1)
|
||||
@ -132,7 +132,7 @@ main {
|
||||
; Midpoint algorithm, filled
|
||||
ubyte x = radius
|
||||
ubyte y = 0
|
||||
byte decisionOver2 = 1-x
|
||||
byte decisionOver2 = 1-x as byte
|
||||
ubyte xx
|
||||
|
||||
while x>=y {
|
||||
|
@ -3,7 +3,6 @@
|
||||
%import c64flt
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
main {
|
||||
const uword width = 30
|
||||
const uword height = 20
|
||||
|
@ -1,7 +1,6 @@
|
||||
%import c64utils
|
||||
%import c64lib
|
||||
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
@ -18,7 +17,7 @@ main {
|
||||
|
||||
irq {
|
||||
|
||||
const ubyte barheight = 4
|
||||
const ubyte barheight = 4 ; should be big enough to re-trigger the Raster irq properly.
|
||||
ubyte[] colors = [6,2,4,5,15,7,1,13,3,12,8,11,9]
|
||||
ubyte color = 0
|
||||
ubyte yanim = 0
|
||||
|
36
examples/strings.p8
Normal file
36
examples/strings.p8
Normal file
@ -0,0 +1,36 @@
|
||||
%import c64lib
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
str s1 = "apple"
|
||||
str s2 = "banana"
|
||||
byte[] a1 = [66,77,88,0]
|
||||
ubyte i1 = 101
|
||||
uword w1 = 000
|
||||
|
||||
c64.STROUT(s1)
|
||||
c64.CHROUT('\n')
|
||||
c64.STROUT(a1)
|
||||
c64.CHROUT('\n')
|
||||
|
||||
c64scr.print("bla\n")
|
||||
|
||||
; c64scr.print_uwhex(s1, true)
|
||||
; w1 = &s1
|
||||
; c64scr.print_uwhex(w1, true)
|
||||
;
|
||||
; c64scr.print_uwhex(a1, true)
|
||||
; w1 = &a1
|
||||
; c64scr.print_uwhex(w1, true)
|
||||
;
|
||||
; s1 = s1
|
||||
; s1 = s2
|
||||
; s2 = "zzz"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,8 +6,6 @@
|
||||
; shows next piece
|
||||
; staged speed increase
|
||||
; some simple sound effects
|
||||
;
|
||||
; @todo show ghost?
|
||||
|
||||
main {
|
||||
|
||||
|
@ -1,10 +1,45 @@
|
||||
%import c64lib
|
||||
%import c64utils
|
||||
%zeropage dontuse
|
||||
%import c64flt
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
|
||||
A += 50
|
||||
|
||||
A += Y + 1
|
||||
A -= Y + 1
|
||||
A += Y - 1
|
||||
A -= Y - 1
|
||||
|
||||
A += Y + 2
|
||||
A -= Y + 2
|
||||
A += Y - 2
|
||||
A -= Y - 2
|
||||
|
||||
; ubyte ubb
|
||||
; byte bb
|
||||
; uword uww
|
||||
; word ww
|
||||
; word ww2
|
||||
;
|
||||
; A = ubb*0
|
||||
; Y = ubb*1
|
||||
; A = ubb*2
|
||||
; Y = ubb*4
|
||||
; A = ubb*8
|
||||
; Y = ubb*16
|
||||
; A = ubb*32
|
||||
; Y = ubb*64
|
||||
; A = ubb*128
|
||||
; Y = ubb+ubb+ubb
|
||||
; A = ubb+ubb+ubb+ubb
|
||||
; ww = ww2+ww2
|
||||
; ww = ww2+ww2+ww2
|
||||
; ww = ww2+ww2+ww2+ww2
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
%import c64flt
|
||||
%import c64graphics
|
||||
%option enable_floats
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
main {
|
||||
|
Reference in New Issue
Block a user