mirror of
https://github.com/irmen/prog8.git
synced 2025-06-15 18:23:35 +00:00
Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
b37231d0f5 | |||
3c55719bf1 | |||
af8279a9b9 | |||
c38508c262 | |||
b0e8738ab8 | |||
cae480768e | |||
a70276c190 | |||
0c461ffe2e | |||
237511f2d6 | |||
cdcb652033 | |||
71e678b382 | |||
3050156325 | |||
4bfdbad2e4 | |||
06137ecdc4 | |||
d89f5b0df8 | |||
b6e2b36692 | |||
a6d789cfbc | |||
c07907e7bd | |||
7d8496c874 | |||
164ac56db1 | |||
fdddb8ca64 | |||
a9d4b8b0fa | |||
ec7b9f54c2 | |||
307558a7e7 | |||
febf423eab | |||
a999c23014 | |||
69f1ade595 | |||
b166576e54 | |||
ee2ba5f398 | |||
cb9825484d | |||
76cda82e23 | |||
37b61d9e6b | |||
52f0222a6d | |||
75ccac2f2c | |||
5c771a91f7 | |||
a242ad10e6 | |||
b5086b6a8f | |||
3e47dad12a | |||
235610f40c | |||
6b59559c65 | |||
23e954f716 | |||
983c899cad | |||
c2f9385965 | |||
ceb2c9e4f8 |
@ -18,7 +18,7 @@ which aims to provide many conveniences over raw assembly code (even when using
|
||||
- modularity, symbol scoping, subroutines
|
||||
- various data types other than just bytes (16-bit words, floats, strings)
|
||||
- automatic variable allocations, automatic string and array variables and string sharing
|
||||
- subroutines with a input- and output parameter signature
|
||||
- subroutines with an input- and output parameter signature
|
||||
- constant folding in expressions
|
||||
- conditional branches
|
||||
- 'when' statement to provide a concise jump table alternative to if/elseif chains
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.0 KiB |
@ -781,3 +781,18 @@ set_array_float .proc
|
||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||
.pend
|
||||
|
||||
|
||||
swap_floats .proc
|
||||
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
|
||||
ldy #4
|
||||
- lda (c64.SCRATCH_ZPWORD1),y
|
||||
pha
|
||||
lda (c64.SCRATCH_ZPWORD2),y
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
pla
|
||||
sta (c64.SCRATCH_ZPWORD2),y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
.pend
|
||||
|
@ -2078,3 +2078,127 @@ ror2_array_uw .proc
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
+ rts
|
||||
.pend
|
||||
|
||||
|
||||
strcpy .proc
|
||||
; copy a string (0-terminated) from A/Y to (ZPWORD1)
|
||||
; it is assumed the target string is large enough.
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
sty c64.SCRATCH_ZPWORD2+1
|
||||
ldy #$ff
|
||||
- iny
|
||||
lda (c64.SCRATCH_ZPWORD2),y
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
bne -
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
func_leftstr .proc
|
||||
; leftstr(source, target, length) with params on stack
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
tay ; length
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD2+1
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
lda #0
|
||||
sta (c64.SCRATCH_ZPWORD2),y
|
||||
- dey
|
||||
cpy #$ff
|
||||
bne +
|
||||
rts
|
||||
+ lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta (c64.SCRATCH_ZPWORD2),y
|
||||
jmp -
|
||||
.pend
|
||||
|
||||
func_rightstr .proc
|
||||
; rightstr(source, target, length) with params on stack
|
||||
; make place for the 4 parameters for substr()
|
||||
dex
|
||||
dex
|
||||
dex
|
||||
dex
|
||||
; X-> .
|
||||
; x+1 -> length of segment
|
||||
; x+2 -> start index
|
||||
; X+3 -> target LO+HI
|
||||
; X+4 -> source LO+HI
|
||||
; original parameters:
|
||||
; x+5 -> original length LO
|
||||
; x+6 -> original targetLO + HI
|
||||
; x+7 -> original sourceLO + HI
|
||||
; replicate paramters:
|
||||
lda c64.ESTACK_LO+5,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
lda c64.ESTACK_LO+6,x
|
||||
sta c64.ESTACK_LO+3,x
|
||||
lda c64.ESTACK_HI+6,x
|
||||
sta c64.ESTACK_HI+3,x
|
||||
lda c64.ESTACK_LO+7,x
|
||||
sta c64.ESTACK_LO+4,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI+7,x
|
||||
sta c64.ESTACK_HI+4,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
; determine string length
|
||||
ldy #0
|
||||
- lda (c64.SCRATCH_ZPWORD1),y
|
||||
beq +
|
||||
iny
|
||||
bne -
|
||||
+ tya
|
||||
sec
|
||||
sbc c64.ESTACK_LO+1,x ; start index = strlen - segment length
|
||||
sta c64.ESTACK_LO+2,x
|
||||
jsr func_substr
|
||||
; unwind original params
|
||||
inx
|
||||
inx
|
||||
inx
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_substr .proc
|
||||
; substr(source, target, start, length) with params on stack
|
||||
inx
|
||||
ldy c64.ESTACK_LO,x ; length
|
||||
inx
|
||||
lda c64.ESTACK_LO,x ; start
|
||||
sta c64.SCRATCH_ZPB1
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD2+1
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
; adjust src location
|
||||
clc
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
adc c64.SCRATCH_ZPB1
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
bcc +
|
||||
inc c64.SCRATCH_ZPWORD1+1
|
||||
+ lda #0
|
||||
sta (c64.SCRATCH_ZPWORD2),y
|
||||
jmp _startloop
|
||||
- lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta (c64.SCRATCH_ZPWORD2),y
|
||||
_startloop dey
|
||||
cpy #$ff
|
||||
bne -
|
||||
rts
|
||||
|
||||
.pend
|
||||
|
@ -1 +1 @@
|
||||
2.1
|
||||
3.0
|
||||
|
@ -102,6 +102,12 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
|
||||
// if the vardecl is a parameter of a subroutine, don't output it again
|
||||
val paramNames = (decl.definingScope() as? Subroutine)?.parameters?.map { it.name }
|
||||
if(paramNames!=null && decl.name in paramNames)
|
||||
return
|
||||
|
||||
when(decl.type) {
|
||||
VarDeclType.VAR -> {}
|
||||
VarDeclType.CONST -> output("const ")
|
||||
@ -304,10 +310,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
|
||||
override fun visit(forLoop: ForLoop) {
|
||||
output("for ")
|
||||
if(forLoop.loopRegister!=null)
|
||||
output(forLoop.loopRegister.toString())
|
||||
else
|
||||
forLoop.loopVar!!.accept(this)
|
||||
forLoop.loopVar.accept(this)
|
||||
output(" in ")
|
||||
forLoop.iterable.accept(this)
|
||||
output(" ")
|
||||
@ -321,16 +324,18 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
whileLoop.body.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(foreverLoop: ForeverLoop) {
|
||||
output("forever ")
|
||||
foreverLoop.body.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop) {
|
||||
output("repeat ")
|
||||
repeatLoop.iterations?.accept(this)
|
||||
output(" ")
|
||||
repeatLoop.body.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(untilLoop: UntilLoop) {
|
||||
output("do ")
|
||||
untilLoop.body.accept(this)
|
||||
output(" until ")
|
||||
repeatLoop.untilCondition.accept(this)
|
||||
untilLoop.untilCondition.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(returnStmt: Return) {
|
||||
@ -346,12 +351,8 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
}
|
||||
|
||||
override fun visit(assignTarget: AssignTarget) {
|
||||
if(assignTarget.register!=null)
|
||||
output(assignTarget.register.toString())
|
||||
else {
|
||||
assignTarget.memoryAddress?.accept(this)
|
||||
assignTarget.identifier?.accept(this)
|
||||
}
|
||||
assignTarget.arrayindexed?.accept(this)
|
||||
}
|
||||
|
||||
@ -392,10 +393,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
outputlni("}}")
|
||||
}
|
||||
|
||||
override fun visit(registerExpr: RegisterExpr) {
|
||||
output(registerExpr.register.toString())
|
||||
}
|
||||
|
||||
override fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
|
||||
output(builtinFunctionStatementPlaceholder.name)
|
||||
}
|
||||
@ -430,10 +427,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
outputln("")
|
||||
}
|
||||
|
||||
override fun visit(structLv: StructLiteralValue) {
|
||||
outputListMembers(structLv.values.asSequence(), '{', '}')
|
||||
}
|
||||
|
||||
override fun visit(nopStatement: NopStatement) {
|
||||
output("; NOP @ ${nopStatement.position} $nopStatement")
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import prog8.ast.base.*
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.*
|
||||
import prog8.functions.BuiltinFunctions
|
||||
@ -58,7 +57,7 @@ interface INameScope {
|
||||
when(stmt) {
|
||||
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
|
||||
is ForLoop -> if(stmt.body.name==name) return stmt.body
|
||||
is RepeatLoop -> if(stmt.body.name==name) return stmt.body
|
||||
is UntilLoop -> if(stmt.body.name==name) return stmt.body
|
||||
is WhileLoop -> if(stmt.body.name==name) return stmt.body
|
||||
is BranchStatement -> {
|
||||
if(stmt.truepart.name==name) return stmt.truepart
|
||||
@ -156,6 +155,7 @@ interface INameScope {
|
||||
}
|
||||
|
||||
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
|
||||
fun containsNoVars() = statements.all { it !is VarDecl }
|
||||
fun containsNoCodeNorVars() = !containsCodeOrVars()
|
||||
|
||||
fun remove(stmt: Statement) {
|
||||
@ -175,8 +175,8 @@ interface INameScope {
|
||||
find(it.truepart)
|
||||
find(it.elsepart)
|
||||
}
|
||||
is UntilLoop -> find(it.body)
|
||||
is RepeatLoop -> find(it.body)
|
||||
is ForeverLoop -> find(it.body)
|
||||
is WhileLoop -> find(it.body)
|
||||
is WhenStatement -> it.choices.forEach { choice->find(choice.statements) }
|
||||
else -> { /* do nothing */ }
|
||||
@ -187,6 +187,14 @@ interface INameScope {
|
||||
find(this)
|
||||
return result
|
||||
}
|
||||
|
||||
fun nextSibling(stmt: Statement): Statement? {
|
||||
val nextIdx = statements.indexOfFirst { it===stmt } + 1
|
||||
return if(nextIdx < statements.size)
|
||||
statements[nextIdx]
|
||||
else
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
interface IAssignable {
|
||||
@ -230,7 +238,7 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(node is Module && replacement is Module)
|
||||
val idx = modules.indexOf(node)
|
||||
val idx = modules.indexOfFirst { it===node }
|
||||
modules[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
@ -257,14 +265,13 @@ class Module(override val name: String,
|
||||
override fun definingScope(): INameScope = program.namespace
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(node is Statement && replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
val idx = statements.indexOfFirst { it===node }
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)"
|
||||
|
||||
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
@ -205,14 +205,14 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||
val forloop = forloop()?.toAst()
|
||||
if(forloop!=null) return forloop
|
||||
|
||||
val repeatloop = repeatloop()?.toAst()
|
||||
if(repeatloop!=null) return repeatloop
|
||||
val untilloop = untilloop()?.toAst()
|
||||
if(untilloop!=null) return untilloop
|
||||
|
||||
val whileloop = whileloop()?.toAst()
|
||||
if(whileloop!=null) return whileloop
|
||||
|
||||
val foreverloop = foreverloop()?.toAst()
|
||||
if(foreverloop!=null) return foreverloop
|
||||
val repeatloop = repeatloop()?.toAst()
|
||||
if(repeatloop!=null) return repeatloop
|
||||
|
||||
val breakstmt = breakstmt()?.toAst()
|
||||
if(breakstmt!=null) return breakstmt
|
||||
@ -247,7 +247,7 @@ private class AsmsubDecl(val name: String,
|
||||
val returntypes: List<DataType>,
|
||||
val asmParameterRegisters: List<RegisterOrStatusflag>,
|
||||
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
|
||||
val asmClobbers: Set<Register>)
|
||||
val asmClobbers: Set<CpuRegister>)
|
||||
|
||||
private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
|
||||
val name = identifier().text
|
||||
@ -274,24 +274,43 @@ private class AsmSubroutineReturn(val type: DataType,
|
||||
val stack: Boolean,
|
||||
val position: Position)
|
||||
|
||||
private fun prog8Parser.ClobberContext.toAst(): Set<Register>
|
||||
= this.register().asSequence().map { it.toAst() }.toSet()
|
||||
|
||||
private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
|
||||
= asmsub_return().map { AsmSubroutineReturn(it.datatype().toAst(), it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) }
|
||||
= asmsub_return().map {
|
||||
val register = it.identifier()?.toAst()
|
||||
var registerorpair: RegisterOrPair? = null
|
||||
var statusregister: Statusflag? = null
|
||||
if(register!=null) {
|
||||
when (val name = register.nameInSource.single()) {
|
||||
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
|
||||
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
|
||||
else -> throw FatalAstException("invalid register or status flag in $it")
|
||||
}
|
||||
}
|
||||
AsmSubroutineReturn(
|
||||
it.datatype().toAst(),
|
||||
registerorpair,
|
||||
statusregister,
|
||||
!it.stack?.text.isNullOrEmpty(), toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
|
||||
= asmsub_param().map {
|
||||
val vardecl = it.vardecl()
|
||||
val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT
|
||||
AsmSubroutineParameter(vardecl.varname.text, datatype,
|
||||
it.registerorpair()?.toAst(),
|
||||
it.statusregister()?.toAst(),
|
||||
val register = it.identifier()?.toAst()
|
||||
var registerorpair: RegisterOrPair? = null
|
||||
var statusregister: Statusflag? = null
|
||||
if(register!=null) {
|
||||
when (val name = register.nameInSource.single()) {
|
||||
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
|
||||
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
|
||||
else -> throw FatalAstException("invalid register or status flag in $it")
|
||||
}
|
||||
}
|
||||
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister,
|
||||
!it.stack?.text.isNullOrEmpty(), toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text)
|
||||
|
||||
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
|
||||
val void = this.VOID() != null
|
||||
val location = scoped_identifier().toAst()
|
||||
@ -350,23 +369,22 @@ private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
||||
}
|
||||
|
||||
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
|
||||
val register = register()?.toAst()
|
||||
val identifier = scoped_identifier()
|
||||
return when {
|
||||
register!=null -> AssignTarget(register, null, null, null, toPosition())
|
||||
identifier!=null -> AssignTarget(null, identifier.toAst(), null, null, toPosition())
|
||||
arrayindexed()!=null -> AssignTarget(null, null, arrayindexed().toAst(), null, toPosition())
|
||||
directmemory()!=null -> AssignTarget(null, null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
|
||||
else -> AssignTarget(null, scoped_identifier()?.toAst(), null, null, toPosition())
|
||||
identifier!=null -> AssignTarget(identifier.toAst(), null, null, toPosition())
|
||||
arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(), null, toPosition())
|
||||
directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
|
||||
else -> AssignTarget(scoped_identifier()?.toAst(), null, null, toPosition())
|
||||
}
|
||||
}
|
||||
|
||||
private fun prog8Parser.RegisterContext.toAst() = Register.valueOf(text.toUpperCase())
|
||||
private fun prog8Parser.ClobberContext.toAst() : Set<CpuRegister> {
|
||||
val names = this.identifier().map { it.toAst().nameInSource.single() }
|
||||
return names.map { CpuRegister.valueOf(it) }.toSet()
|
||||
}
|
||||
|
||||
private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase())
|
||||
|
||||
private fun prog8Parser.RegisterorpairContext.toAst() = RegisterOrPair.valueOf(text.toUpperCase())
|
||||
|
||||
private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex =
|
||||
ArrayIndex(expression().toAst(), toPosition())
|
||||
|
||||
@ -469,18 +487,11 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
||||
// the ConstantFold takes care of that and converts the type if needed.
|
||||
ArrayLiteralValue(InferredTypes.InferredType.unknown(), array, position = litval.toPosition())
|
||||
}
|
||||
litval.structliteral()!=null -> {
|
||||
val values = litval.structliteral().expression().map { it.toAst() }
|
||||
StructLiteralValue(values, litval.toPosition())
|
||||
}
|
||||
else -> throw FatalAstException("invalid parsed literal")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(register()!=null)
|
||||
return RegisterExpr(register().toAst(), register().toPosition())
|
||||
|
||||
if(scoped_identifier()!=null)
|
||||
return scoped_identifier().toAst()
|
||||
|
||||
@ -572,15 +583,14 @@ private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
|
||||
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
|
||||
|
||||
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
|
||||
val loopregister = register()?.toAst()
|
||||
val loopvar = identifier()?.toAst()
|
||||
val loopvar = identifier().toAst()
|
||||
val iterable = expression()!!.toAst()
|
||||
val scope =
|
||||
if(statement()!=null)
|
||||
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
|
||||
else
|
||||
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
|
||||
return ForLoop(loopregister, loopvar, iterable, scope, toPosition())
|
||||
return ForLoop(loopvar, iterable, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition())
|
||||
@ -595,19 +605,20 @@ private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
|
||||
return WhileLoop(condition, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.ForeverloopContext.toAst(): ForeverLoop {
|
||||
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
|
||||
val iterations = expression()?.toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||
?: statement().toPosition())
|
||||
return ForeverLoop(scope, toPosition())
|
||||
return RepeatLoop(iterations, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
|
||||
private fun prog8Parser.UntilloopContext.toAst(): UntilLoop {
|
||||
val untilCondition = expression().toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||
?: statement().toPosition())
|
||||
return RepeatLoop(scope, untilCondition, toPosition())
|
||||
return UntilLoop(scope, untilCondition, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {
|
||||
|
@ -64,7 +64,7 @@ enum class DataType {
|
||||
}
|
||||
}
|
||||
|
||||
enum class Register {
|
||||
enum class CpuRegister {
|
||||
A,
|
||||
X,
|
||||
Y
|
||||
@ -76,14 +76,23 @@ enum class RegisterOrPair {
|
||||
Y,
|
||||
AX,
|
||||
AY,
|
||||
XY
|
||||
XY;
|
||||
|
||||
companion object {
|
||||
val names by lazy { values().map { it.toString()} }
|
||||
}
|
||||
|
||||
} // only used in parameter and return value specs in asm subroutines
|
||||
|
||||
enum class Statusflag {
|
||||
Pc,
|
||||
Pz,
|
||||
Pv,
|
||||
Pn
|
||||
Pn;
|
||||
|
||||
companion object {
|
||||
val names by lazy { values().map { it.toString()} }
|
||||
}
|
||||
}
|
||||
|
||||
enum class BranchCondition {
|
||||
|
@ -2,7 +2,7 @@ package prog8.ast.base
|
||||
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
|
||||
class FatalAstException (override var message: String) : Exception(message)
|
||||
open class FatalAstException (override var message: String) : Exception(message)
|
||||
|
||||
open class AstException (override var message: String) : Exception(message)
|
||||
|
||||
|
@ -6,7 +6,6 @@ import prog8.ast.processing.*
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
import prog8.optimizer.AssignmentTransformer
|
||||
import prog8.optimizer.FlattenAnonymousScopesAndNopRemover
|
||||
|
||||
|
||||
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
|
||||
@ -32,6 +31,11 @@ internal fun Program.addTypecasts(errors: ErrorReporter) {
|
||||
caster.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.verifyFunctionArgTypes() {
|
||||
val fixer = VerifyFunctionArgTypes(this)
|
||||
fixer.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.transformAssignments(errors: ErrorReporter) {
|
||||
val transform = AssignmentTransformer(this, errors)
|
||||
transform.visit(this)
|
||||
@ -56,21 +60,23 @@ internal fun Program.checkRecursion(errors: ErrorReporter) {
|
||||
}
|
||||
|
||||
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
|
||||
val checker = AstIdentifiersChecker(this, errors)
|
||||
checker.visit(this)
|
||||
|
||||
val checker2 = AstIdentifiersChecker(this, errors)
|
||||
checker2.visit(this)
|
||||
|
||||
if(errors.isEmpty()) {
|
||||
val transforms = AstVariousTransforms(this)
|
||||
transforms.visit(this)
|
||||
transforms.applyModifications()
|
||||
}
|
||||
|
||||
if (modules.map { it.name }.toSet().size != modules.size) {
|
||||
throw FatalAstException("modules should all be unique")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.makeForeverLoops() {
|
||||
val checker = ForeverLoopsMaker()
|
||||
checker.visit(this)
|
||||
checker.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.removeNopsFlattenAnonScopes() {
|
||||
val flattener = FlattenAnonymousScopesAndNopRemover()
|
||||
flattener.visit(this)
|
||||
internal fun Program.variousCleanups() {
|
||||
val process = VariousCleanups()
|
||||
process.visit(this)
|
||||
process.applyModifications()
|
||||
}
|
||||
|
@ -4,11 +4,11 @@ import prog8.ast.*
|
||||
import prog8.ast.antlr.escape
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.functions.CannotEvaluateException
|
||||
import prog8.functions.NotConstArgumentException
|
||||
import prog8.functions.builtinFunctionReturnType
|
||||
import java.util.*
|
||||
@ -20,31 +20,28 @@ val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "=
|
||||
|
||||
sealed class Expression: Node {
|
||||
abstract fun constValue(program: Program): NumericLiteralValue?
|
||||
abstract fun accept(visitor: IAstModifyingVisitor): Expression
|
||||
abstract fun accept(visitor: IAstVisitor)
|
||||
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||
abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this and add identifier usage tracking into CallGraph instead
|
||||
abstract fun referencesIdentifiers(vararg name: String): Boolean
|
||||
abstract fun inferType(program: Program): InferredTypes.InferredType
|
||||
|
||||
infix fun isSameAs(other: Expression): Boolean {
|
||||
if(this===other)
|
||||
return true
|
||||
when(this) {
|
||||
is RegisterExpr ->
|
||||
return (other is RegisterExpr && other.register==register)
|
||||
return when(this) {
|
||||
is IdentifierReference ->
|
||||
return (other is IdentifierReference && other.nameInSource==nameInSource)
|
||||
(other is IdentifierReference && other.nameInSource==nameInSource)
|
||||
is PrefixExpression ->
|
||||
return (other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
|
||||
(other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
|
||||
is BinaryExpression ->
|
||||
return (other is BinaryExpression && other.operator==operator
|
||||
(other is BinaryExpression && other.operator==operator
|
||||
&& other.left isSameAs left
|
||||
&& other.right isSameAs right)
|
||||
is ArrayIndexedExpression -> {
|
||||
return (other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
|
||||
(other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
|
||||
&& other.arrayspec.index isSameAs arrayspec.index)
|
||||
}
|
||||
else -> return other==this
|
||||
else -> other==this
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -65,7 +62,6 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -123,7 +119,6 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
// binary expression should actually have been optimized away into a single value, before const value was requested...
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -237,7 +232,6 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -274,7 +268,6 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -309,7 +302,6 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
}
|
||||
@ -328,7 +320,6 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -388,7 +379,6 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun constValue(program: Program) = this
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -465,32 +455,6 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
}
|
||||
}
|
||||
|
||||
class StructLiteralValue(var values: List<Expression>,
|
||||
override val position: Position): Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent=parent
|
||||
values.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = values.any { it.referencesIdentifiers(*name) }
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STRUCT)
|
||||
|
||||
override fun toString(): String {
|
||||
return "struct{ ${values.joinToString(", ")} }"
|
||||
}
|
||||
}
|
||||
|
||||
private var heapIdSequence = 0 // unique ids for strings and arrays "on the heap"
|
||||
|
||||
class StringLiteralValue(val value: String,
|
||||
@ -510,7 +474,6 @@ class StringLiteralValue(val value: String,
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -539,19 +502,18 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression)
|
||||
val idx = value.indexOf(node)
|
||||
val idx = value.indexOfFirst { it===node }
|
||||
value[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String = "$value"
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = if(type.isUnknown) type else guessDatatype(program)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = if(type.isKnown) type else guessDatatype(program)
|
||||
|
||||
operator fun compareTo(other: ArrayLiteralValue): Int = throw ExpressionError("cannot order compare arrays", position)
|
||||
override fun hashCode(): Int = Objects.hash(value, type)
|
||||
@ -640,7 +602,6 @@ class RangeExpr(var from: Expression,
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -708,30 +669,6 @@ internal fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
|
||||
}
|
||||
}
|
||||
|
||||
class RegisterExpr(val register: Register, override val position: Position) : Expression(), IAssignable {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String): Boolean = register.name in name
|
||||
override fun toString(): String {
|
||||
return "RegisterExpr(register=$register, pos=$position)"
|
||||
}
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
|
||||
}
|
||||
|
||||
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(), IAssignable {
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -744,6 +681,9 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl
|
||||
fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine
|
||||
|
||||
override fun equals(other: Any?) = other is IdentifierReference && other.nameInSource==nameInSource
|
||||
override fun hashCode() = nameInSource.hashCode()
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
@ -768,7 +708,6 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
return "IdentifierRef($nameInSource)"
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -812,7 +751,7 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
if(node===target)
|
||||
target=replacement as IdentifierReference
|
||||
else {
|
||||
val idx = args.indexOf(node)
|
||||
val idx = args.indexOfFirst { it===node }
|
||||
args[idx] = replacement as Expression
|
||||
}
|
||||
replacement.parent = this
|
||||
@ -848,13 +787,16 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
// const-evaluating the builtin function call failed.
|
||||
return null
|
||||
}
|
||||
catch(x: CannotEvaluateException) {
|
||||
// const-evaluating the builtin function call failed.
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "FunctionCall(target=$target, pos=$position)"
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
|
@ -110,22 +110,11 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(forLoop: ForLoop) {
|
||||
if(forLoop.body.containsNoCodeNorVars())
|
||||
errors.warn("for loop body is empty", forLoop.position)
|
||||
|
||||
val iterableDt = forLoop.iterable.inferType(program).typeOrElse(DataType.BYTE)
|
||||
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
|
||||
errors.err("can only loop over an iterable type", forLoop.position)
|
||||
} else {
|
||||
if (forLoop.loopRegister != null) {
|
||||
// loop register
|
||||
if (iterableDt != DataType.ARRAY_UB && iterableDt != DataType.ARRAY_B && iterableDt != DataType.STR)
|
||||
errors.err("register can only loop over bytes", forLoop.position)
|
||||
if(forLoop.loopRegister!=Register.A)
|
||||
errors.err("it's only possible to use A as a loop register", forLoop.position)
|
||||
} else {
|
||||
// loop variable
|
||||
val loopvar = forLoop.loopVar!!.targetVarDecl(program.namespace)
|
||||
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace)
|
||||
if(loopvar==null || loopvar.type== VarDeclType.CONST) {
|
||||
errors.err("for loop requires a variable to loop with", forLoop.position)
|
||||
} else {
|
||||
@ -155,7 +144,6 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(forLoop)
|
||||
}
|
||||
@ -260,27 +248,27 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
val regCounts = mutableMapOf<Register, Int>().withDefault { 0 }
|
||||
val regCounts = mutableMapOf<CpuRegister, Int>().withDefault { 0 }
|
||||
val statusflagCounts = mutableMapOf<Statusflag, Int>().withDefault { 0 }
|
||||
fun countRegisters(from: Iterable<RegisterOrStatusflag>) {
|
||||
regCounts.clear()
|
||||
statusflagCounts.clear()
|
||||
for(p in from) {
|
||||
when(p.registerOrPair) {
|
||||
RegisterOrPair.A -> regCounts[Register.A]=regCounts.getValue(Register.A)+1
|
||||
RegisterOrPair.X -> regCounts[Register.X]=regCounts.getValue(Register.X)+1
|
||||
RegisterOrPair.Y -> regCounts[Register.Y]=regCounts.getValue(Register.Y)+1
|
||||
RegisterOrPair.A -> regCounts[CpuRegister.A]=regCounts.getValue(CpuRegister.A)+1
|
||||
RegisterOrPair.X -> regCounts[CpuRegister.X]=regCounts.getValue(CpuRegister.X)+1
|
||||
RegisterOrPair.Y -> regCounts[CpuRegister.Y]=regCounts.getValue(CpuRegister.Y)+1
|
||||
RegisterOrPair.AX -> {
|
||||
regCounts[Register.A]=regCounts.getValue(Register.A)+1
|
||||
regCounts[Register.X]=regCounts.getValue(Register.X)+1
|
||||
regCounts[CpuRegister.A]=regCounts.getValue(CpuRegister.A)+1
|
||||
regCounts[CpuRegister.X]=regCounts.getValue(CpuRegister.X)+1
|
||||
}
|
||||
RegisterOrPair.AY -> {
|
||||
regCounts[Register.A]=regCounts.getValue(Register.A)+1
|
||||
regCounts[Register.Y]=regCounts.getValue(Register.Y)+1
|
||||
regCounts[CpuRegister.A]=regCounts.getValue(CpuRegister.A)+1
|
||||
regCounts[CpuRegister.Y]=regCounts.getValue(CpuRegister.Y)+1
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
regCounts[Register.X]=regCounts.getValue(Register.X)+1
|
||||
regCounts[Register.Y]=regCounts.getValue(Register.Y)+1
|
||||
regCounts[CpuRegister.X]=regCounts.getValue(CpuRegister.X)+1
|
||||
regCounts[CpuRegister.Y]=regCounts.getValue(CpuRegister.Y)+1
|
||||
}
|
||||
null ->
|
||||
if(p.statusflag!=null)
|
||||
@ -325,17 +313,13 @@ internal class AstChecker(private val program: Program,
|
||||
visitStatements(subroutine.statements)
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop) {
|
||||
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)
|
||||
super.visit(repeatLoop)
|
||||
override fun visit(untilLoop: UntilLoop) {
|
||||
if(untilLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
errors.err("condition value should be an integer type", untilLoop.untilCondition.position)
|
||||
super.visit(untilLoop)
|
||||
}
|
||||
|
||||
override fun visit(whileLoop: WhileLoop) {
|
||||
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)
|
||||
super.visit(whileLoop)
|
||||
@ -361,9 +345,9 @@ internal class AstChecker(private val program: Program,
|
||||
if(targetIdent!=null) {
|
||||
val targetVar = targetIdent.targetVarDecl(program.namespace)
|
||||
if(targetVar?.struct != null) {
|
||||
val sourceStructLv = assignment.value as? StructLiteralValue
|
||||
val sourceStructLv = assignment.value as? ArrayLiteralValue
|
||||
if (sourceStructLv != null) {
|
||||
if (sourceStructLv.values.size != targetVar.struct?.numberOfElements)
|
||||
if (sourceStructLv.value.size != targetVar.struct?.numberOfElements)
|
||||
errors.err("number of elements doesn't match struct definition", sourceStructLv.position)
|
||||
} else {
|
||||
val sourceIdent = assignment.value as? IdentifierReference
|
||||
@ -397,8 +381,7 @@ internal class AstChecker(private val program: Program,
|
||||
val targetIdentifier = assignTarget.identifier
|
||||
if (targetIdentifier != null) {
|
||||
val targetName = targetIdentifier.nameInSource
|
||||
val targetSymbol = program.namespace.lookup(targetName, assignment)
|
||||
when (targetSymbol) {
|
||||
when (val targetSymbol = program.namespace.lookup(targetName, assignment)) {
|
||||
null -> {
|
||||
errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position)
|
||||
return
|
||||
@ -463,7 +446,6 @@ 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")
|
||||
}
|
||||
@ -518,21 +500,14 @@ internal class AstChecker(private val program: Program,
|
||||
checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteralValue)
|
||||
}
|
||||
is ArrayLiteralValue -> {
|
||||
val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteralValue)
|
||||
checkValueTypeAndRangeArray(decl.datatype, decl.struct, arraySpec, decl.value as ArrayLiteralValue)
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue)
|
||||
}
|
||||
is StructLiteralValue -> {
|
||||
if(decl.datatype==DataType.STRUCT) {
|
||||
val struct = decl.struct!!
|
||||
val structLv = decl.value as StructLiteralValue
|
||||
if(struct.numberOfElements != structLv.values.size) {
|
||||
val structLv = decl.value as ArrayLiteralValue
|
||||
if(struct.numberOfElements != structLv.value.size) {
|
||||
errors.err("struct value has incorrect number of elements", structLv.position)
|
||||
return
|
||||
}
|
||||
for(value in structLv.values.zip(struct.statements)) {
|
||||
for(value in structLv.value.zip(struct.statements)) {
|
||||
val memberdecl = value.second as VarDecl
|
||||
val constValue = value.first.constValue(program)
|
||||
if(constValue==null) {
|
||||
@ -546,9 +521,13 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errors.err("struct literal is wrong type to initialize this variable", decl.value!!.position)
|
||||
val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteralValue)
|
||||
checkValueTypeAndRangeArray(decl.datatype, decl.struct, arraySpec, decl.value as ArrayLiteralValue)
|
||||
}
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue)
|
||||
}
|
||||
else -> {
|
||||
err("var/const declaration needs a compile-time constant initializer value, or range, instead found: ${decl.value!!.javaClass.simpleName}")
|
||||
super.visit(decl)
|
||||
@ -585,8 +564,18 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
val declValue = decl.value
|
||||
if(declValue!=null && decl.type==VarDeclType.VAR && !declValue.inferType(program).istype(decl.datatype))
|
||||
if(declValue!=null && decl.type==VarDeclType.VAR) {
|
||||
if(decl.datatype==DataType.STRUCT) {
|
||||
val valueIdt = declValue.inferType(program)
|
||||
if(valueIdt.isUnknown)
|
||||
throw AstException("invalid value type")
|
||||
val valueDt = valueIdt.typeOrElse(DataType.STRUCT)
|
||||
if(valueDt !in ArrayDatatypes)
|
||||
err("initialisation of struct should be with array value", declValue.position)
|
||||
} else if (!declValue.inferType(program).istype(decl.datatype)) {
|
||||
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(decl)
|
||||
}
|
||||
@ -733,7 +722,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
"**" -> {
|
||||
if(leftDt in IntegerDatatypes)
|
||||
errors.err("power operator requires floating point", expr.position)
|
||||
errors.err("power operator requires floating point operands", expr.position)
|
||||
}
|
||||
"and", "or", "xor" -> {
|
||||
// only integer numeric operands accepted, and if literal constants, only boolean values accepted (0 or 1)
|
||||
@ -845,7 +834,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 }) {
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
@ -1293,8 +1282,8 @@ internal class AstChecker(private val program: Program,
|
||||
DataType.STR -> sourceDatatype== DataType.STR
|
||||
DataType.STRUCT -> {
|
||||
if(sourceDatatype==DataType.STRUCT) {
|
||||
val structLv = sourceValue as StructLiteralValue
|
||||
val numValues = structLv.values.size
|
||||
val structLv = sourceValue as ArrayLiteralValue
|
||||
val numValues = structLv.value.size
|
||||
val targetstruct = target.identifier!!.targetVarDecl(program.namespace)!!.struct!!
|
||||
return targetstruct.numberOfElements == numValues
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
@ -10,61 +8,38 @@ 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 {
|
||||
internal class AstIdentifiersChecker(private val program: Program, private val errors: ErrorReporter) : IAstVisitor {
|
||||
private var blocks = mutableMapOf<String, Block>()
|
||||
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
|
||||
|
||||
private fun nameError(name: String, position: Position, existing: Statement) {
|
||||
errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)
|
||||
}
|
||||
|
||||
override fun visit(module: Module) {
|
||||
vardeclsToAdd.clear()
|
||||
blocks.clear() // blocks may be redefined within a different module
|
||||
|
||||
super.visit(module)
|
||||
// add any new vardecls to the various scopes
|
||||
for((where, decls) in vardeclsToAdd) {
|
||||
where.statements.addAll(0, decls)
|
||||
decls.forEach { it.linkParents(where as Node) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(block: Block): Statement {
|
||||
override fun visit(block: Block) {
|
||||
val existing = blocks[block.name]
|
||||
if(existing!=null)
|
||||
nameError(block.name, block.position, existing)
|
||||
else
|
||||
blocks[block.name] = block
|
||||
|
||||
return super.visit(block)
|
||||
super.visit(block)
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall): Expression {
|
||||
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
|
||||
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
|
||||
val typecast = TypecastExpression(functionCall.args.single(), DataType.UBYTE, false, functionCall.position)
|
||||
typecast.linkParents(functionCall.parent)
|
||||
return super.visit(typecast)
|
||||
}
|
||||
return super.visit(functionCall)
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
// first, check if there are datatype errors on the vardecl
|
||||
override fun visit(decl: VarDecl) {
|
||||
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
|
||||
|
||||
// now check the identifier
|
||||
if(decl.name in BuiltinFunctions)
|
||||
// the builtin functions can't be redefined
|
||||
errors.err("builtin function cannot be redefined", decl.position)
|
||||
|
||||
if(decl.name in CompilationTarget.machine.opcodeNames)
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
|
||||
|
||||
// is it a struct variable? then define all its struct members as mangled names,
|
||||
// and include the original decl as well.
|
||||
if(decl.datatype==DataType.STRUCT) {
|
||||
if (decl.structHasBeenFlattened)
|
||||
return super.visit(decl) // don't do this multiple times
|
||||
@ -82,26 +57,20 @@ internal class AstIdentifiersChecker(private val program: Program,
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
if(decl.value != null && decl.value !is StructLiteralValue) {
|
||||
errors.err("initializing requires struct literal value", decl.value?.position ?: decl.position)
|
||||
if (decl.value != null && decl.value !is ArrayLiteralValue) {
|
||||
errors.err("initializing a struct requires array literal value", decl.value?.position ?: decl.position)
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
val decls = decl.flattenStructMembers()
|
||||
decls.add(decl)
|
||||
val result = AnonymousScope(decls, decl.position)
|
||||
result.linkParents(decl.parent)
|
||||
return result
|
||||
}
|
||||
|
||||
val existing = program.namespace.lookup(listOf(decl.name), decl)
|
||||
if (existing != null && existing !== decl)
|
||||
nameError(decl.name, decl.position, existing)
|
||||
|
||||
return super.visit(decl)
|
||||
super.visit(decl)
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine): Statement {
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
if(subroutine.name in CompilationTarget.machine.opcodeNames) {
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
|
||||
} else if(subroutine.name in BuiltinFunctions) {
|
||||
@ -138,30 +107,15 @@ internal class AstIdentifiersChecker(private val program: Program,
|
||||
nameError(name, sub.position, subroutine)
|
||||
}
|
||||
|
||||
// inject subroutine params as local variables (if they're not there yet) (for non-kernel subroutines and non-asm parameters)
|
||||
// NOTE:
|
||||
// - numeric types BYTE and WORD and FLOAT are passed by value;
|
||||
// - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter)
|
||||
if(subroutine.asmAddress==null) {
|
||||
if(subroutine.asmParameterRegisters.isEmpty()) {
|
||||
subroutine.parameters
|
||||
.filter { it.name !in namesInSub }
|
||||
.forEach {
|
||||
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
|
||||
vardecl.linkParents(subroutine)
|
||||
subroutine.statements.add(0, vardecl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
||||
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
|
||||
}
|
||||
}
|
||||
return super.visit(subroutine)
|
||||
|
||||
super.visit(subroutine)
|
||||
}
|
||||
|
||||
override fun visit(label: Label): Statement {
|
||||
override fun visit(label: Label) {
|
||||
if(label.name in CompilationTarget.machine.opcodeNames)
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
|
||||
|
||||
@ -179,163 +133,24 @@ internal class AstIdentifiersChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visit(label)
|
||||
|
||||
super.visit(label)
|
||||
}
|
||||
|
||||
override fun visit(forLoop: ForLoop): Statement {
|
||||
// If the for loop has a decltype, it means to declare the loopvar inside the loop body
|
||||
// rather than reusing an already declared loopvar from an outer scope.
|
||||
// For loops that loop over an interable variable (instead of a range of numbers) get an
|
||||
// additional interation count variable in their scope.
|
||||
if(forLoop.loopRegister!=null) {
|
||||
if(forLoop.loopRegister == Register.X)
|
||||
errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
|
||||
} else {
|
||||
val loopVar = forLoop.loopVar
|
||||
if (loopVar != null) {
|
||||
val validName = forLoop.body.name.replace("<", "").replace(">", "").replace("-", "")
|
||||
val loopvarName = "prog8_loopvar_$validName"
|
||||
if (forLoop.iterable !is RangeExpr) {
|
||||
val existing = if (forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(loopvarName), forLoop.body.statements.first())
|
||||
if (existing == null) {
|
||||
// create loop iteration counter variable (without value, to avoid an assignment)
|
||||
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE, null, loopvarName, null, null,
|
||||
isArray = false, autogeneratedDontRemove = true, position = loopVar.position)
|
||||
vardecl.linkParents(forLoop.body)
|
||||
forLoop.body.statements.add(0, vardecl)
|
||||
loopVar.parent = forLoop.body // loopvar 'is defined in the body'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visit(forLoop)
|
||||
}
|
||||
|
||||
override fun visit(assignTarget: AssignTarget): AssignTarget {
|
||||
if(assignTarget.register== Register.X)
|
||||
errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
|
||||
return super.visit(assignTarget)
|
||||
}
|
||||
|
||||
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
|
||||
val array = super.visit(arrayLiteral)
|
||||
if(array is ArrayLiteralValue) {
|
||||
val vardecl = array.parent as? VarDecl
|
||||
// adjust the datatype of the array (to an educated guess)
|
||||
if(vardecl!=null) {
|
||||
val arrayDt = array.type
|
||||
if(!arrayDt.istype(vardecl.datatype)) {
|
||||
val cast = array.cast(vardecl.datatype)
|
||||
if (cast != null) {
|
||||
vardecl.value = cast
|
||||
cast.linkParents(vardecl)
|
||||
return cast
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
else {
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if(arrayDt.isKnown) {
|
||||
// this array literal is part of an expression, turn it into an identifier reference
|
||||
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||
return if (litval2 != null) {
|
||||
litval2.parent = array.parent
|
||||
makeIdentifierFromRefLv(litval2)
|
||||
} else array
|
||||
}
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
override fun visit(stringLiteral: StringLiteralValue): Expression {
|
||||
val string = super.visit(stringLiteral)
|
||||
if(string is StringLiteralValue) {
|
||||
val vardecl = string.parent as? VarDecl
|
||||
// intern the string; move it into the heap
|
||||
override fun visit(string: StringLiteralValue) {
|
||||
if (string.value.length !in 1..255)
|
||||
errors.err("string literal length must be between 1 and 255", string.position)
|
||||
return if (vardecl != null)
|
||||
string
|
||||
else
|
||||
makeIdentifierFromRefLv(string) // replace the literal string by a identifier reference.
|
||||
}
|
||||
return string
|
||||
|
||||
super.visit(string)
|
||||
}
|
||||
|
||||
private fun makeIdentifierFromRefLv(array: ArrayLiteralValue): IdentifierReference {
|
||||
// a referencetype literal value that's not declared as a variable
|
||||
// we need to introduce an auto-generated variable for this to be able to refer to the value
|
||||
// note: if the var references the same literal value, it is not yet de-duplicated here.
|
||||
val scope = array.definingScope()
|
||||
val variable = VarDecl.createAuto(array)
|
||||
return replaceWithIdentifier(variable, scope, array.parent)
|
||||
}
|
||||
|
||||
private fun makeIdentifierFromRefLv(string: StringLiteralValue): IdentifierReference {
|
||||
// a referencetype literal value that's not declared as a variable
|
||||
// we need to introduce an auto-generated variable for this to be able to refer to the value
|
||||
// note: if the var references the same literal value, it is not yet de-duplicated here.
|
||||
val scope = string.definingScope()
|
||||
val variable = VarDecl.createAuto(string)
|
||||
return replaceWithIdentifier(variable, scope, string.parent)
|
||||
}
|
||||
|
||||
private fun replaceWithIdentifier(variable: VarDecl, scope: INameScope, parent: Node): IdentifierReference {
|
||||
val variable1 = addVarDecl(scope, variable)
|
||||
// replace the reference literal by a identifier reference
|
||||
val identifier = IdentifierReference(listOf(variable1.name), variable1.position)
|
||||
identifier.parent = parent
|
||||
return identifier
|
||||
}
|
||||
|
||||
override fun visit(structDecl: StructDecl): Statement {
|
||||
override fun visit(structDecl: StructDecl) {
|
||||
for(member in structDecl.statements){
|
||||
val decl = member as? VarDecl
|
||||
if(decl!=null && decl.datatype !in NumericDatatypes)
|
||||
errors.err("structs can only contain numerical types", decl.position)
|
||||
}
|
||||
|
||||
return super.visit(structDecl)
|
||||
}
|
||||
|
||||
override fun visit(expr: BinaryExpression): Expression {
|
||||
return when {
|
||||
expr.left is StringLiteralValue ->
|
||||
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr)
|
||||
expr.right is StringLiteralValue ->
|
||||
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr)
|
||||
else -> super.visit(expr)
|
||||
super.visit(structDecl)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
|
||||
val constvalue = operand.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
if (expr.operator == "*") {
|
||||
// repeat a string a number of times
|
||||
return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position)
|
||||
}
|
||||
}
|
||||
if(expr.operator == "+" && operand is StringLiteralValue) {
|
||||
// concatenate two strings
|
||||
return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position)
|
||||
}
|
||||
return expr
|
||||
}
|
||||
|
||||
private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl {
|
||||
if(scope !in vardeclsToAdd)
|
||||
vardeclsToAdd[scope] = mutableListOf()
|
||||
val declList = vardeclsToAdd.getValue(scope)
|
||||
val existing = declList.singleOrNull { it.name==variable.name }
|
||||
return if(existing!=null) {
|
||||
existing
|
||||
} else {
|
||||
declList.add(variable)
|
||||
variable
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
176
compiler/src/prog8/ast/processing/AstVariousTransforms.kt
Normal file
176
compiler/src/prog8/ast/processing/AstVariousTransforms.kt
Normal file
@ -0,0 +1,176 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.target.nameInSource == listOf("swap")) {
|
||||
// if x and y are both just identifiers, do not rewrite (there should be asm generation for that)
|
||||
// otherwise:
|
||||
// rewrite swap(x,y) as follows:
|
||||
// - declare a temp variable of the same datatype
|
||||
// - temp = x, x = y, y= temp
|
||||
val first = functionCallStatement.args[0]
|
||||
val second = functionCallStatement.args[1]
|
||||
if(first !is IdentifierReference && second !is IdentifierReference) {
|
||||
val dt = first.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
val tempname = "prog8_swaptmp_${functionCallStatement.hashCode()}"
|
||||
val tempvardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, tempname, null, null, isArray = false, autogeneratedDontRemove = true, position = first.position)
|
||||
val tempvar = IdentifierReference(listOf(tempname), first.position)
|
||||
val assignTemp = Assignment(
|
||||
AssignTarget(tempvar, null, null, first.position),
|
||||
null,
|
||||
first,
|
||||
first.position
|
||||
)
|
||||
val assignFirst = Assignment(
|
||||
AssignTarget.fromExpr(first),
|
||||
null,
|
||||
second,
|
||||
first.position
|
||||
)
|
||||
val assignSecond = Assignment(
|
||||
AssignTarget.fromExpr(second),
|
||||
null,
|
||||
tempvar,
|
||||
first.position
|
||||
)
|
||||
val scope = AnonymousScope(mutableListOf(tempvardecl, assignTemp, assignFirst, assignSecond), first.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, scope, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
|
||||
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
|
||||
val typecast = TypecastExpression(functionCall.args.single(), DataType.UBYTE, false, functionCall.position)
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
functionCall, typecast, parent
|
||||
))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// is it a struct variable? then define all its struct members as mangled names,
|
||||
// and include the original decl as well.
|
||||
if(decl.datatype==DataType.STRUCT && !decl.structHasBeenFlattened) {
|
||||
val decls = decl.flattenStructMembers()
|
||||
decls.add(decl)
|
||||
val result = AnonymousScope(decls, decl.position)
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
decl, result, parent
|
||||
))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
// For non-kernel subroutines and non-asm parameters:
|
||||
// inject subroutine params as local variables (if they're not there yet).
|
||||
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||
if(subroutine.asmAddress==null) {
|
||||
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
|
||||
val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet()
|
||||
if(!vars.containsAll(subroutine.parameters.map{it.name})) {
|
||||
return subroutine.parameters
|
||||
.filter { it.name !in namesInSub }
|
||||
.map {
|
||||
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
|
||||
IAstModification.InsertFirst(vardecl, subroutine)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
when {
|
||||
expr.left is StringLiteralValue ->
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr),
|
||||
parent
|
||||
))
|
||||
expr.right is StringLiteralValue ->
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr),
|
||||
parent
|
||||
))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
if(string.parent !is VarDecl) {
|
||||
// replace the literal string by a identifier reference to a new local vardecl
|
||||
val vardecl = VarDecl.createAuto(string)
|
||||
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(string, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl, string.definingScope() as Node)
|
||||
)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
val vardecl = array.parent as? VarDecl
|
||||
if(vardecl!=null) {
|
||||
// adjust the datatype of the array (to an educated guess)
|
||||
val arrayDt = array.type
|
||||
if(!arrayDt.istype(vardecl.datatype)) {
|
||||
val cast = array.cast(vardecl.datatype)
|
||||
if (cast != null && cast!=array)
|
||||
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
|
||||
}
|
||||
} else {
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if(arrayDt.isKnown) {
|
||||
// this array literal is part of an expression, turn it into an identifier reference
|
||||
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||
if(litval2!=null && litval2!=array) {
|
||||
val vardecl2 = VarDecl.createAuto(litval2)
|
||||
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(array, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl2, array.definingScope() as Node)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
|
||||
val constvalue = operand.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
if (expr.operator == "*") {
|
||||
// repeat a string a number of times
|
||||
return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position)
|
||||
}
|
||||
}
|
||||
if(expr.operator == "+" && operand is StringLiteralValue) {
|
||||
// concatenate two strings
|
||||
return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position)
|
||||
}
|
||||
return expr
|
||||
}
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
@ -15,7 +12,7 @@ interface IAstModification {
|
||||
class Remove(val node: Node, val parent: Node) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
if (!parent.statements.remove(node))
|
||||
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
|
||||
throw FatalAstException("attempt to remove non-existing node $node")
|
||||
} else {
|
||||
throw FatalAstException("parent of a remove modification is not an INameScope")
|
||||
@ -55,7 +52,7 @@ interface IAstModification {
|
||||
class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
val idx = parent.statements.indexOf(after)+1
|
||||
val idx = parent.statements.indexOfFirst { it===after } + 1
|
||||
parent.statements.add(idx, stmt)
|
||||
stmt.linkParents(parent)
|
||||
} else {
|
||||
@ -97,7 +94,7 @@ abstract class AstWalker {
|
||||
open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(foreverLoop: ForeverLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
@ -113,13 +110,11 @@ abstract class AstWalker {
|
||||
open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(program: Program, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(registerExpr: RegisterExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
@ -141,7 +136,7 @@ abstract class AstWalker {
|
||||
open fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(foreverLoop: ForeverLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
@ -157,13 +152,11 @@ abstract class AstWalker {
|
||||
open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(program: Program, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(registerExpr: RegisterExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
@ -328,7 +321,7 @@ abstract class AstWalker {
|
||||
|
||||
fun visit(forLoop: ForLoop, parent: Node) {
|
||||
track(before(forLoop, parent), forLoop, parent)
|
||||
forLoop.loopVar?.accept(this, forLoop)
|
||||
forLoop.loopVar.accept(this, forLoop)
|
||||
forLoop.iterable.accept(this, forLoop)
|
||||
forLoop.body.accept(this, forLoop)
|
||||
track(after(forLoop, parent), forLoop, parent)
|
||||
@ -341,19 +334,20 @@ abstract class AstWalker {
|
||||
track(after(whileLoop, parent), whileLoop, parent)
|
||||
}
|
||||
|
||||
fun visit(foreverLoop: ForeverLoop, parent: Node) {
|
||||
track(before(foreverLoop, parent), foreverLoop, parent)
|
||||
foreverLoop.body.accept(this, foreverLoop)
|
||||
track(after(foreverLoop, parent), foreverLoop, parent)
|
||||
}
|
||||
|
||||
fun visit(repeatLoop: RepeatLoop, parent: Node) {
|
||||
track(before(repeatLoop, parent), repeatLoop, parent)
|
||||
repeatLoop.untilCondition.accept(this, repeatLoop)
|
||||
repeatLoop.iterations?.accept(this, repeatLoop)
|
||||
repeatLoop.body.accept(this, repeatLoop)
|
||||
track(after(repeatLoop, parent), repeatLoop, parent)
|
||||
}
|
||||
|
||||
fun visit(untilLoop: UntilLoop, parent: Node) {
|
||||
track(before(untilLoop, parent), untilLoop, parent)
|
||||
untilLoop.untilCondition.accept(this, untilLoop)
|
||||
untilLoop.body.accept(this, untilLoop)
|
||||
track(after(untilLoop, parent), untilLoop, parent)
|
||||
}
|
||||
|
||||
fun visit(returnStmt: Return, parent: Node) {
|
||||
track(before(returnStmt, parent), returnStmt, parent)
|
||||
returnStmt.value?.accept(this, returnStmt)
|
||||
@ -410,11 +404,6 @@ abstract class AstWalker {
|
||||
track(after(inlineAssembly, parent), inlineAssembly, parent)
|
||||
}
|
||||
|
||||
fun visit(registerExpr: RegisterExpr, parent: Node) {
|
||||
track(before(registerExpr, parent), registerExpr, parent)
|
||||
track(after(registerExpr, parent), registerExpr, parent)
|
||||
}
|
||||
|
||||
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node) {
|
||||
track(before(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
|
||||
track(after(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
|
||||
@ -444,11 +433,5 @@ abstract class AstWalker {
|
||||
structDecl.statements.forEach { it.accept(this, structDecl) }
|
||||
track(after(structDecl, parent), structDecl, parent)
|
||||
}
|
||||
|
||||
fun visit(structLv: StructLiteralValue, parent: Node) {
|
||||
track(before(structLv, parent), structLv, parent)
|
||||
structLv.values.forEach { it.accept(this, structLv) }
|
||||
track(after(structLv, parent), structLv, parent)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,28 +0,0 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.ForeverLoop
|
||||
import prog8.ast.statements.RepeatLoop
|
||||
import prog8.ast.statements.WhileLoop
|
||||
|
||||
|
||||
internal class ForeverLoopsMaker: AstWalker() {
|
||||
override fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
|
||||
val numeric = repeatLoop.untilCondition as? NumericLiteralValue
|
||||
if(numeric!=null && numeric.number.toInt() == 0) {
|
||||
val forever = ForeverLoop(repeatLoop.body, repeatLoop.position)
|
||||
return listOf(IAstModification.ReplaceNode(repeatLoop, forever, parent))
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||
val numeric = whileLoop.condition as? NumericLiteralValue
|
||||
if(numeric!=null && numeric.number.toInt() != 0) {
|
||||
val forever = ForeverLoop(whileLoop.body, whileLoop.position)
|
||||
return listOf(IAstModification.ReplaceNode(whileLoop, forever, parent))
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
}
|
@ -1,267 +0,0 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
// TODO replace all occurrences of this with AstWalker
|
||||
interface IAstModifyingVisitor {
|
||||
fun visit(program: Program) {
|
||||
program.modules.forEach { it.accept(this) }
|
||||
}
|
||||
|
||||
fun visit(module: Module) {
|
||||
module.statements = module.statements.map { it.accept(this) }.toMutableList()
|
||||
}
|
||||
|
||||
fun visit(expr: PrefixExpression): Expression {
|
||||
expr.expression = expr.expression.accept(this)
|
||||
return expr
|
||||
}
|
||||
|
||||
fun visit(expr: BinaryExpression): Expression {
|
||||
expr.left = expr.left.accept(this)
|
||||
expr.right = expr.right.accept(this)
|
||||
return expr
|
||||
}
|
||||
|
||||
fun visit(directive: Directive): Statement {
|
||||
return directive
|
||||
}
|
||||
|
||||
fun visit(block: Block): Statement {
|
||||
block.statements = block.statements.map { it.accept(this) }.toMutableList()
|
||||
return block
|
||||
}
|
||||
|
||||
fun visit(decl: VarDecl): Statement {
|
||||
decl.value = decl.value?.accept(this)
|
||||
decl.arraysize?.accept(this)
|
||||
return decl
|
||||
}
|
||||
|
||||
fun visit(subroutine: Subroutine): Statement {
|
||||
subroutine.statements = subroutine.statements.map { it.accept(this) }.toMutableList()
|
||||
return subroutine
|
||||
}
|
||||
|
||||
fun visit(functionCall: FunctionCall): Expression {
|
||||
val newtarget = functionCall.target.accept(this)
|
||||
if(newtarget is IdentifierReference)
|
||||
functionCall.target = newtarget
|
||||
else
|
||||
throw FatalAstException("cannot change class of function call target")
|
||||
functionCall.args = functionCall.args.map { it.accept(this) }.toMutableList()
|
||||
return functionCall
|
||||
}
|
||||
|
||||
fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||
val newtarget = functionCallStatement.target.accept(this)
|
||||
if(newtarget is IdentifierReference)
|
||||
functionCallStatement.target = newtarget
|
||||
else
|
||||
throw FatalAstException("cannot change class of function call target")
|
||||
functionCallStatement.args = functionCallStatement.args.map { it.accept(this) }.toMutableList()
|
||||
return functionCallStatement
|
||||
}
|
||||
|
||||
fun visit(identifier: IdentifierReference): Expression {
|
||||
// note: this is an identifier that is used in an expression.
|
||||
// other identifiers are simply part of the other statements (such as jumps, subroutine defs etc)
|
||||
return identifier
|
||||
}
|
||||
|
||||
fun visit(jump: Jump): Statement {
|
||||
if(jump.identifier!=null) {
|
||||
val ident = jump.identifier.accept(this)
|
||||
if(ident is IdentifierReference && ident!==jump.identifier) {
|
||||
return Jump(null, ident, null, jump.position)
|
||||
}
|
||||
}
|
||||
return jump
|
||||
}
|
||||
|
||||
fun visit(ifStatement: IfStatement): Statement {
|
||||
ifStatement.condition = ifStatement.condition.accept(this)
|
||||
ifStatement.truepart = ifStatement.truepart.accept(this) as AnonymousScope
|
||||
ifStatement.elsepart = ifStatement.elsepart.accept(this) as AnonymousScope
|
||||
return ifStatement
|
||||
}
|
||||
|
||||
fun visit(branchStatement: BranchStatement): Statement {
|
||||
branchStatement.truepart = branchStatement.truepart.accept(this) as AnonymousScope
|
||||
branchStatement.elsepart = branchStatement.elsepart.accept(this) as AnonymousScope
|
||||
return branchStatement
|
||||
}
|
||||
|
||||
fun visit(range: RangeExpr): Expression {
|
||||
range.from = range.from.accept(this)
|
||||
range.to = range.to.accept(this)
|
||||
range.step = range.step.accept(this)
|
||||
return range
|
||||
}
|
||||
|
||||
fun visit(label: Label): Statement {
|
||||
return label
|
||||
}
|
||||
|
||||
fun visit(literalValue: NumericLiteralValue): NumericLiteralValue {
|
||||
return literalValue
|
||||
}
|
||||
|
||||
fun visit(stringLiteral: StringLiteralValue): Expression {
|
||||
return stringLiteral
|
||||
}
|
||||
|
||||
fun visit(arrayLiteral: ArrayLiteralValue): Expression {
|
||||
for(av in arrayLiteral.value.withIndex()) {
|
||||
val newvalue = av.value.accept(this)
|
||||
arrayLiteral.value[av.index] = newvalue
|
||||
}
|
||||
return arrayLiteral
|
||||
}
|
||||
|
||||
fun visit(assignment: Assignment): Statement {
|
||||
assignment.target = assignment.target.accept(this)
|
||||
assignment.value = assignment.value.accept(this)
|
||||
return assignment
|
||||
}
|
||||
|
||||
fun visit(postIncrDecr: PostIncrDecr): Statement {
|
||||
postIncrDecr.target = postIncrDecr.target.accept(this)
|
||||
return postIncrDecr
|
||||
}
|
||||
|
||||
fun visit(contStmt: Continue): Statement {
|
||||
return contStmt
|
||||
}
|
||||
|
||||
fun visit(breakStmt: Break): Statement {
|
||||
return breakStmt
|
||||
}
|
||||
|
||||
fun visit(forLoop: ForLoop): Statement {
|
||||
when(val newloopvar = forLoop.loopVar?.accept(this)) {
|
||||
is IdentifierReference -> forLoop.loopVar = newloopvar
|
||||
null -> forLoop.loopVar = null
|
||||
else -> throw FatalAstException("can't change class of loopvar")
|
||||
}
|
||||
forLoop.iterable = forLoop.iterable.accept(this)
|
||||
forLoop.body = forLoop.body.accept(this) as AnonymousScope
|
||||
return forLoop
|
||||
}
|
||||
|
||||
fun visit(whileLoop: WhileLoop): Statement {
|
||||
whileLoop.condition = whileLoop.condition.accept(this)
|
||||
whileLoop.body = whileLoop.body.accept(this) as AnonymousScope
|
||||
return whileLoop
|
||||
}
|
||||
|
||||
fun visit(foreverLoop: ForeverLoop): Statement {
|
||||
foreverLoop.body = foreverLoop.body.accept(this) as AnonymousScope
|
||||
return foreverLoop
|
||||
}
|
||||
|
||||
fun visit(repeatLoop: RepeatLoop): Statement {
|
||||
repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this)
|
||||
repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope
|
||||
return repeatLoop
|
||||
}
|
||||
|
||||
fun visit(returnStmt: Return): Statement {
|
||||
returnStmt.value = returnStmt.value?.accept(this)
|
||||
return returnStmt
|
||||
}
|
||||
|
||||
fun visit(arrayIndexedExpression: ArrayIndexedExpression): ArrayIndexedExpression {
|
||||
val ident = arrayIndexedExpression.identifier.accept(this)
|
||||
if(ident is IdentifierReference)
|
||||
arrayIndexedExpression.identifier = ident
|
||||
arrayIndexedExpression.arrayspec.accept(this)
|
||||
return arrayIndexedExpression
|
||||
}
|
||||
|
||||
fun visit(assignTarget: AssignTarget): AssignTarget {
|
||||
when (val ident = assignTarget.identifier?.accept(this)) {
|
||||
is IdentifierReference -> assignTarget.identifier = ident
|
||||
null -> assignTarget.identifier = null
|
||||
else -> throw FatalAstException("can't change class of assign target identifier")
|
||||
}
|
||||
assignTarget.arrayindexed = assignTarget.arrayindexed?.accept(this)
|
||||
assignTarget.memoryAddress?.let { visit(it) }
|
||||
return assignTarget
|
||||
}
|
||||
|
||||
fun visit(scope: AnonymousScope): Statement {
|
||||
scope.statements = scope.statements.map { it.accept(this) }.toMutableList()
|
||||
return scope
|
||||
}
|
||||
|
||||
fun visit(typecast: TypecastExpression): Expression {
|
||||
typecast.expression = typecast.expression.accept(this)
|
||||
return typecast
|
||||
}
|
||||
|
||||
fun visit(memread: DirectMemoryRead): Expression {
|
||||
memread.addressExpression = memread.addressExpression.accept(this)
|
||||
return memread
|
||||
}
|
||||
|
||||
fun visit(memwrite: DirectMemoryWrite) {
|
||||
memwrite.addressExpression = memwrite.addressExpression.accept(this)
|
||||
}
|
||||
|
||||
fun visit(addressOf: AddressOf): Expression {
|
||||
val ident = addressOf.identifier.accept(this)
|
||||
if(ident is IdentifierReference)
|
||||
addressOf.identifier = ident
|
||||
else
|
||||
throw FatalAstException("can't change class of addressof identifier")
|
||||
return addressOf
|
||||
}
|
||||
|
||||
fun visit(inlineAssembly: InlineAssembly): Statement {
|
||||
return inlineAssembly
|
||||
}
|
||||
|
||||
fun visit(registerExpr: RegisterExpr): Expression {
|
||||
return registerExpr
|
||||
}
|
||||
|
||||
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): Statement {
|
||||
return builtinFunctionStatementPlaceholder
|
||||
}
|
||||
|
||||
fun visit(nopStatement: NopStatement): Statement {
|
||||
return nopStatement
|
||||
}
|
||||
|
||||
fun visit(whenStatement: WhenStatement): Statement {
|
||||
whenStatement.condition = whenStatement.condition.accept(this)
|
||||
whenStatement.choices.forEach { it.accept(this) }
|
||||
return whenStatement
|
||||
}
|
||||
|
||||
fun visit(whenChoice: WhenChoice) {
|
||||
whenChoice.values = whenChoice.values?.map { it.accept(this) }
|
||||
val stmt = whenChoice.statements.accept(this)
|
||||
if(stmt is AnonymousScope)
|
||||
whenChoice.statements = stmt
|
||||
else {
|
||||
whenChoice.statements = AnonymousScope(mutableListOf(stmt), stmt.position)
|
||||
whenChoice.statements.linkParents(whenChoice)
|
||||
}
|
||||
}
|
||||
|
||||
fun visit(structDecl: StructDecl): Statement {
|
||||
structDecl.statements = structDecl.statements.map{ it.accept(this) }.toMutableList()
|
||||
return structDecl
|
||||
}
|
||||
|
||||
fun visit(structLv: StructLiteralValue): Expression {
|
||||
structLv.values = structLv.values.map { it.accept(this) }
|
||||
return structLv
|
||||
}
|
||||
}
|
@ -102,7 +102,7 @@ interface IAstVisitor {
|
||||
}
|
||||
|
||||
fun visit(forLoop: ForLoop) {
|
||||
forLoop.loopVar?.accept(this)
|
||||
forLoop.loopVar.accept(this)
|
||||
forLoop.iterable.accept(this)
|
||||
forLoop.body.accept(this)
|
||||
}
|
||||
@ -112,13 +112,14 @@ interface IAstVisitor {
|
||||
whileLoop.body.accept(this)
|
||||
}
|
||||
|
||||
fun visit(foreverLoop: ForeverLoop) {
|
||||
foreverLoop.body.accept(this)
|
||||
fun visit(repeatLoop: RepeatLoop) {
|
||||
repeatLoop.iterations?.accept(this)
|
||||
repeatLoop.body.accept(this)
|
||||
}
|
||||
|
||||
fun visit(repeatLoop: RepeatLoop) {
|
||||
repeatLoop.untilCondition.accept(this)
|
||||
repeatLoop.body.accept(this)
|
||||
fun visit(untilLoop: UntilLoop) {
|
||||
untilLoop.untilCondition.accept(this)
|
||||
untilLoop.body.accept(this)
|
||||
}
|
||||
|
||||
fun visit(returnStmt: Return) {
|
||||
@ -159,9 +160,6 @@ interface IAstVisitor {
|
||||
fun visit(inlineAssembly: InlineAssembly) {
|
||||
}
|
||||
|
||||
fun visit(registerExpr: RegisterExpr) {
|
||||
}
|
||||
|
||||
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
|
||||
}
|
||||
|
||||
@ -181,8 +179,4 @@ interface IAstVisitor {
|
||||
fun visit(structDecl: StructDecl) {
|
||||
structDecl.statements.forEach { it.accept(this) }
|
||||
}
|
||||
|
||||
fun visit(structLv: StructLiteralValue) {
|
||||
structLv.values.forEach { it.accept(this) }
|
||||
}
|
||||
}
|
||||
|
@ -10,11 +10,12 @@ internal class ImportedModuleDirectiveRemover: AstWalker() {
|
||||
*/
|
||||
|
||||
private val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
|
||||
if(directive.directive in moduleLevelDirectives) {
|
||||
return listOf(IAstModification.Remove(directive, parent))
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
// - sorts the choices in when statement.
|
||||
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||
|
||||
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
|
||||
|
||||
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
|
||||
@ -33,7 +33,7 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
}
|
||||
|
||||
reorderVardeclsAndDirectives(module.statements)
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
|
||||
@ -56,7 +56,7 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
}
|
||||
|
||||
reorderVardeclsAndDirectives(block.statements)
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
@ -68,7 +68,7 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
)
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
@ -78,7 +78,7 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
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 target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, null, declValue, decl.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(decl, assign, parent),
|
||||
@ -86,7 +86,7 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
)
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
||||
@ -95,7 +95,7 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
}
|
||||
whenStatement.choices.clear()
|
||||
choices.mapTo(whenStatement.choices) { it.second }
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
@ -106,8 +106,8 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
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 = { ..... } '
|
||||
val assignments = if (assignment.value is ArrayLiteralValue) {
|
||||
flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = [ ..... ] '
|
||||
} else {
|
||||
flattenStructAssignmentFromIdentifier(assignment, program) // 'structvar1 = structvar2'
|
||||
}
|
||||
@ -119,7 +119,7 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
}
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> {
|
||||
@ -128,15 +128,15 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
val struct = targetVar.struct!!
|
||||
|
||||
val slv = structAssignment.value as? StructLiteralValue
|
||||
if(slv==null || slv.values.size != struct.numberOfElements)
|
||||
val slv = structAssignment.value as? ArrayLiteralValue
|
||||
if(slv==null || slv.value.size != struct.numberOfElements)
|
||||
throw FatalAstException("element count mismatch")
|
||||
|
||||
return struct.statements.zip(slv.values).map { (targetDecl, sourceValue) ->
|
||||
return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) ->
|
||||
targetDecl as VarDecl
|
||||
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
|
||||
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position),
|
||||
null, sourceValue, sourceValue.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
@ -168,13 +168,13 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
|
||||
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
|
||||
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position),
|
||||
null, sourceIdref, member.second.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
is StructLiteralValue -> {
|
||||
is ArrayLiteralValue -> {
|
||||
throw IllegalArgumentException("not going to flatten a structLv assignment here")
|
||||
}
|
||||
else -> throw FatalAstException("strange struct value")
|
||||
|
@ -16,6 +16,8 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
* (this includes function call arguments)
|
||||
*/
|
||||
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
@ -32,7 +34,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
@ -72,7 +74,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
@ -85,7 +87,9 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
|
||||
private fun afterFunctionCallArgs(call: IFunctionCall, scope: INameScope): Iterable<IAstModification> {
|
||||
// see if a typecast is needed to convert the arguments into the required parameter's type
|
||||
return when(val sub = call.target.targetStatement(scope)) {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
when(val sub = call.target.targetStatement(scope)) {
|
||||
is Subroutine -> {
|
||||
for(arg in sub.parameters.zip(call.args.withIndex())) {
|
||||
val argItype = arg.second.value.inferType(program)
|
||||
@ -94,26 +98,33 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
val requiredType = arg.first.type
|
||||
if (requiredType != argtype) {
|
||||
if (argtype isAssignableTo requiredType) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position),
|
||||
call as Node))
|
||||
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(
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
|
||||
call as Node))
|
||||
call as Node)
|
||||
} else if(arg.second.value is NumericLiteralValue) {
|
||||
try {
|
||||
val castedValue = (arg.second.value as NumericLiteralValue).cast(requiredType)
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
castedValue,
|
||||
call as Node)
|
||||
} catch (x: ExpressionError) {
|
||||
// no cast possible
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
emptyList()
|
||||
}
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(sub.name)
|
||||
if(func.pure) {
|
||||
// non-pure functions don't get automatic typecasts because sometimes they act directly on their parameters
|
||||
for (arg in func.parameters.zip(call.args.withIndex())) {
|
||||
val argItype = arg.second.value.inferType(program)
|
||||
if (argItype.isKnown) {
|
||||
@ -122,20 +133,20 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
continue
|
||||
for (possibleType in arg.first.possibleDatatypes) {
|
||||
if (argtype isAssignableTo possibleType) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position),
|
||||
call as Node))
|
||||
call as Node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
emptyList()
|
||||
}
|
||||
null -> emptyList()
|
||||
null -> { }
|
||||
else -> throw FatalAstException("call to something weird $sub ${call.target}")
|
||||
}
|
||||
|
||||
return modifications
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
@ -143,7 +154,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
errors.warn("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
@ -154,7 +165,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
||||
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
|
||||
@ -165,52 +176,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
||||
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun after(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
// assignment of a struct literal value, some member values may need proper typecast
|
||||
|
||||
fun addTypecastsIfNeeded(struct: StructDecl): Iterable<IAstModification> {
|
||||
val newValues = struct.statements.zip(structLv.values).map { (structMemberDecl, memberValue) ->
|
||||
val memberDt = (structMemberDecl as VarDecl).datatype
|
||||
val valueDt = memberValue.inferType(program)
|
||||
if (valueDt.typeOrElse(memberDt) != memberDt)
|
||||
TypecastExpression(memberValue, memberDt, true, memberValue.position)
|
||||
else
|
||||
memberValue
|
||||
}
|
||||
|
||||
class StructLvValueReplacer(val targetStructLv: StructLiteralValue, val typecastValues: List<Expression>) : IAstModification {
|
||||
override fun perform() {
|
||||
targetStructLv.values = typecastValues
|
||||
typecastValues.forEach { it.linkParents(targetStructLv) }
|
||||
}
|
||||
}
|
||||
|
||||
return if(structLv.values.zip(newValues).any { (v1, v2) -> v1 !== v2})
|
||||
listOf(StructLvValueReplacer(structLv, newValues))
|
||||
else
|
||||
emptyList()
|
||||
}
|
||||
|
||||
val decl = structLv.parent as? VarDecl
|
||||
if(decl != null) {
|
||||
val struct = decl.struct
|
||||
if(struct != null)
|
||||
return addTypecastsIfNeeded(struct)
|
||||
} else {
|
||||
val assign = structLv.parent as? Assignment
|
||||
if (assign != null) {
|
||||
val decl2 = assign.target.identifier?.targetVarDecl(program.namespace)
|
||||
if(decl2 != null) {
|
||||
val struct = decl2.struct
|
||||
if(struct != null)
|
||||
return addTypecastsIfNeeded(struct)
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
@ -221,7 +187,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
if(subroutine.returntypes.size==1) {
|
||||
val subReturnType = subroutine.returntypes.first()
|
||||
if (returnValue.inferType(program).istype(subReturnType))
|
||||
return emptyList()
|
||||
return noModifications
|
||||
if (returnValue is NumericLiteralValue) {
|
||||
returnStmt.value = returnValue.cast(subroutine.returntypes.single())
|
||||
} else {
|
||||
@ -232,6 +198,6 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
43
compiler/src/prog8/ast/processing/VariousCleanups.kt
Normal file
43
compiler/src/prog8/ast/processing/VariousCleanups.kt
Normal file
@ -0,0 +1,43 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.NopStatement
|
||||
|
||||
|
||||
internal class VariousCleanups: AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.Remove(nopStatement, parent))
|
||||
}
|
||||
|
||||
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
return if(parent is INameScope)
|
||||
listOf(ScopeFlatten(scope, parent as INameScope))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
|
||||
override fun perform() {
|
||||
val idx = into.statements.indexOf(scope)
|
||||
if(idx>=0) {
|
||||
into.statements.addAll(idx+1, scope.statements)
|
||||
into.statements.remove(scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(typecast.expression is NumericLiteralValue) {
|
||||
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
|
||||
return listOf(IAstModification.ReplaceNode(typecast, value, parent))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
}
|
42
compiler/src/prog8/ast/processing/VerifyFunctionArgTypes.kt
Normal file
42
compiler/src/prog8/ast/processing/VerifyFunctionArgTypes.kt
Normal file
@ -0,0 +1,42 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(functionCall: FunctionCall)
|
||||
= checkTypes(functionCall as IFunctionCall, functionCall.definingScope())
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement)
|
||||
= checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope())
|
||||
|
||||
private fun checkTypes(call: IFunctionCall, scope: INameScope) {
|
||||
val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) }
|
||||
val target = call.target.targetStatement(scope)
|
||||
when(target) {
|
||||
is Subroutine -> {
|
||||
val paramtypes = target.parameters.map { it.type }
|
||||
if(argtypes!=paramtypes)
|
||||
throw CompilerException("parameter type mismatch $call")
|
||||
}
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(target.name)
|
||||
val paramtypes = func.parameters.map { it.possibleDatatypes }
|
||||
for(x in argtypes.zip(paramtypes)) {
|
||||
if(x.first !in x.second)
|
||||
throw CompilerException("parameter type mismatch $call")
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,12 +4,10 @@ import prog8.ast.*
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
|
||||
|
||||
sealed class Statement : Node {
|
||||
abstract fun accept(visitor: IAstModifyingVisitor) : Statement
|
||||
abstract fun accept(visitor: IAstVisitor)
|
||||
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||
|
||||
@ -31,8 +29,6 @@ sealed class Statement : Node {
|
||||
return scope.joinToString(".")
|
||||
}
|
||||
|
||||
abstract val expensiveToInline: Boolean
|
||||
|
||||
fun definingBlock(): Block {
|
||||
if(this is Block)
|
||||
return this
|
||||
@ -44,14 +40,12 @@ sealed class Statement : Node {
|
||||
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() {
|
||||
override var parent: Node = ParentSentinel
|
||||
override fun linkParents(parent: Node) {}
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
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) {
|
||||
replacement.parent = this
|
||||
}
|
||||
override val expensiveToInline = false
|
||||
}
|
||||
|
||||
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean)
|
||||
@ -62,8 +56,6 @@ class Block(override val name: String,
|
||||
val isInLibrary: Boolean,
|
||||
override val position: Position) : Statement(), INameScope {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline
|
||||
get() = statements.any { it.expensiveToInline }
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -72,12 +64,11 @@ class Block(override val name: String,
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
val idx = statements.indexOfFirst { it ===node }
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -90,7 +81,6 @@ class Block(override val name: String,
|
||||
|
||||
data class Directive(val directive: String, val args: List<DirectiveArg>, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = false
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -98,7 +88,6 @@ data class Directive(val directive: String, val args: List<DirectiveArg>, overri
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
@ -114,14 +103,12 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over
|
||||
|
||||
data class Label(val name: String, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = false
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -132,7 +119,6 @@ data class Label(val name: String, override val position: Position) : Statement(
|
||||
|
||||
open class Return(var value: Expression?, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = value!=null && value !is NumericLiteralValue
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -145,7 +131,6 @@ open class Return(var value: Expression?, override val position: Position) : Sta
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -155,7 +140,6 @@ open class Return(var value: Expression?, override val position: Position) : Sta
|
||||
}
|
||||
|
||||
class ReturnFromIrq(override val position: Position) : Return(null, position) {
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
|
||||
override fun toString(): String {
|
||||
@ -166,28 +150,24 @@ class ReturnFromIrq(override val position: Position) : Return(null, position) {
|
||||
|
||||
class Continue(override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = false
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent=parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
class Break(override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = false
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent=parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
@ -217,9 +197,6 @@ open class VarDecl(val type: VarDeclType,
|
||||
var structHasBeenFlattened = false // set later
|
||||
private set
|
||||
|
||||
override val expensiveToInline
|
||||
get() = value!=null && value !is NumericLiteralValue
|
||||
|
||||
// prefix for literal values that are turned into a variable on the heap
|
||||
|
||||
companion object {
|
||||
@ -281,7 +258,6 @@ open class VarDecl(val type: VarDeclType,
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -294,7 +270,7 @@ open class VarDecl(val type: VarDeclType,
|
||||
fun flattenStructMembers(): MutableList<Statement> {
|
||||
val result = struct!!.statements.withIndex().map {
|
||||
val member = it.value as VarDecl
|
||||
val initvalue = if(value!=null) (value as StructLiteralValue).values[it.index] else null
|
||||
val initvalue = if(value!=null) (value as ArrayLiteralValue).value[it.index] else null
|
||||
VarDecl(
|
||||
VarDeclType.VAR,
|
||||
member.datatype,
|
||||
@ -338,11 +314,8 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
|
||||
}
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstModifyingVisitor) {
|
||||
index = index.accept(visitor)
|
||||
}
|
||||
fun accept(visitor: IAstVisitor) = index.accept(visitor)
|
||||
fun accept(visitor: AstWalker, parent: Node) = index.accept(visitor, parent)
|
||||
fun accept(visitor: AstWalker, parent: Node) = index.accept(visitor, this)
|
||||
|
||||
override fun toString(): String {
|
||||
return("ArrayIndex($index, pos=$position)")
|
||||
@ -353,8 +326,6 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
|
||||
|
||||
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
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -371,7 +342,6 @@ open class Assignment(var target: AssignTarget, var aug_op : String?, var value:
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -384,7 +354,6 @@ open class Assignment(var target: AssignTarget, var aug_op : String?, var value:
|
||||
|
||||
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)
|
||||
@ -404,8 +373,7 @@ open class Assignment(var target: AssignTarget, var aug_op : String?, var value:
|
||||
}
|
||||
}
|
||||
|
||||
data class AssignTarget(val register: Register?,
|
||||
var identifier: IdentifierReference?,
|
||||
data class AssignTarget(var identifier: IdentifierReference?,
|
||||
var arrayindexed: ArrayIndexedExpression?,
|
||||
val memoryAddress: DirectMemoryWrite?,
|
||||
override val position: Position) : Node {
|
||||
@ -427,26 +395,21 @@ data class AssignTarget(val register: Register?,
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
companion object {
|
||||
fun fromExpr(expr: Expression): AssignTarget {
|
||||
return when (expr) {
|
||||
is RegisterExpr -> AssignTarget(expr.register, null, null, null, expr.position)
|
||||
is IdentifierReference -> AssignTarget(null, expr, null, null, expr.position)
|
||||
is ArrayIndexedExpression -> AssignTarget(null, null, expr, null, expr.position)
|
||||
is DirectMemoryRead -> AssignTarget(null, null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
|
||||
is IdentifierReference -> AssignTarget(expr, null, null, expr.position)
|
||||
is ArrayIndexedExpression -> AssignTarget(null, expr, null, expr.position)
|
||||
is DirectMemoryRead -> AssignTarget(null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
|
||||
else -> throw FatalAstException("invalid expression object $expr")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType {
|
||||
if(register!=null)
|
||||
return InferredTypes.knownFor(DataType.UBYTE)
|
||||
|
||||
if(identifier!=null) {
|
||||
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
|
||||
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
|
||||
@ -471,7 +434,6 @@ data class AssignTarget(val register: Register?,
|
||||
else
|
||||
false
|
||||
}
|
||||
this.register!=null -> value is RegisterExpr && value.register==register
|
||||
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier!!.nameInSource
|
||||
this.arrayindexed!=null -> value is ArrayIndexedExpression &&
|
||||
value.identifier.nameInSource==arrayindexed!!.identifier.nameInSource &&
|
||||
@ -485,8 +447,6 @@ data class AssignTarget(val register: Register?,
|
||||
fun isSameAs(other: AssignTarget, program: Program): Boolean {
|
||||
if(this===other)
|
||||
return true
|
||||
if(this.register!=null && other.register!=null)
|
||||
return this.register==other.register
|
||||
if(this.identifier!=null && other.identifier!=null)
|
||||
return this.identifier!!.nameInSource==other.identifier!!.nameInSource
|
||||
if(this.memoryAddress!=null && other.memoryAddress!=null) {
|
||||
@ -505,8 +465,6 @@ data class AssignTarget(val register: Register?,
|
||||
}
|
||||
|
||||
fun isNotMemory(namespace: INameScope): Boolean {
|
||||
if(this.register!=null)
|
||||
return true
|
||||
if(this.memoryAddress!=null)
|
||||
return false
|
||||
if(this.arrayindexed!=null) {
|
||||
@ -525,7 +483,6 @@ data class AssignTarget(val register: Register?,
|
||||
|
||||
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = false
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -538,7 +495,6 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -552,7 +508,6 @@ class Jump(val address: Int?,
|
||||
val generatedLabel: String?, // used in code generation scenarios
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = false
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -560,7 +515,6 @@ class Jump(val address: Int?,
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -574,8 +528,6 @@ class FunctionCallStatement(override var target: IdentifierReference,
|
||||
val void: Boolean,
|
||||
override val position: Position) : Statement(), IFunctionCall {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline
|
||||
get() = args.any { it !is NumericLiteralValue }
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -587,13 +539,12 @@ class FunctionCallStatement(override var target: IdentifierReference,
|
||||
if(node===target)
|
||||
target = replacement as IdentifierReference
|
||||
else {
|
||||
val idx = args.indexOf(node)
|
||||
val idx = args.indexOfFirst { it===node }
|
||||
args[idx] = replacement as Expression
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -604,14 +555,12 @@ class FunctionCallStatement(override var target: IdentifierReference,
|
||||
|
||||
class InlineAssembly(val assembly: String, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = true
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
@ -620,8 +569,6 @@ class AnonymousScope(override var statements: MutableList<Statement>,
|
||||
override val position: Position) : INameScope, Statement() {
|
||||
override val name: String
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline
|
||||
get() = statements.any { it.expensiveToInline }
|
||||
|
||||
companion object {
|
||||
private var sequenceNumber = 1
|
||||
@ -639,36 +586,25 @@ class AnonymousScope(override var statements: MutableList<Statement>,
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
val idx = statements.indexOfFirst { it===node }
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
class NopStatement(override val position: Position): Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = false
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
companion object {
|
||||
fun insteadOf(stmt: Statement): NopStatement {
|
||||
val nop = NopStatement(stmt.position)
|
||||
nop.parent = stmt.parent
|
||||
return nop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the subroutine class covers both the normal user-defined subroutines,
|
||||
@ -679,20 +615,14 @@ class Subroutine(override val name: String,
|
||||
val returntypes: List<DataType>,
|
||||
val asmParameterRegisters: List<RegisterOrStatusflag>,
|
||||
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
|
||||
val asmClobbers: Set<Register>,
|
||||
val asmClobbers: Set<CpuRegister>,
|
||||
val asmAddress: Int?,
|
||||
val isAsmSubroutine: Boolean,
|
||||
override var statements: MutableList<Statement>,
|
||||
override val position: Position) : Statement(), INameScope {
|
||||
|
||||
var keepAlways: Boolean = false
|
||||
override val expensiveToInline
|
||||
get() = statements.any { it.expensiveToInline }
|
||||
|
||||
override lateinit var parent: Node
|
||||
val calledBy = mutableListOf<Node>()
|
||||
val calls = mutableSetOf<Subroutine>()
|
||||
|
||||
val scopedname: String by lazy { makeScopedName(name) }
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@ -703,12 +633,11 @@ class Subroutine(override val name: String,
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
val idx = statements.indexOfFirst { it===node }
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -745,8 +674,6 @@ class IfStatement(var condition: Expression,
|
||||
var elsepart: AnonymousScope,
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline: Boolean
|
||||
get() = truepart.expensiveToInline || elsepart.expensiveToInline
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -765,7 +692,6 @@ class IfStatement(var condition: Expression,
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -776,8 +702,6 @@ class BranchStatement(var condition: BranchCondition,
|
||||
var elsepart: AnonymousScope,
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline: Boolean
|
||||
get() = truepart.expensiveToInline || elsepart.expensiveToInline
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -794,23 +718,20 @@ class BranchStatement(var condition: BranchCondition,
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
}
|
||||
|
||||
class ForLoop(val loopRegister: Register?,
|
||||
var loopVar: IdentifierReference?,
|
||||
class ForLoop(var loopVar: IdentifierReference,
|
||||
var iterable: Expression,
|
||||
var body: AnonymousScope,
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = true
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent=parent
|
||||
loopVar?.linkParents(this)
|
||||
loopVar.linkParents(this)
|
||||
iterable.linkParents(this)
|
||||
body.linkParents(this)
|
||||
}
|
||||
@ -825,26 +746,20 @@ class ForLoop(val loopRegister: Register?,
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String {
|
||||
return "ForLoop(loopVar: $loopVar, loopReg: $loopRegister, iterable: $iterable, pos=$position)"
|
||||
return "ForLoop(loopVar: $loopVar, iterable: $iterable, pos=$position)"
|
||||
}
|
||||
|
||||
fun loopVarDt(program: Program): InferredTypes.InferredType {
|
||||
val lv = loopVar
|
||||
return if(loopRegister!=null) InferredTypes.InferredType.known(DataType.UBYTE)
|
||||
else lv?.inferType(program) ?: InferredTypes.InferredType.unknown()
|
||||
}
|
||||
fun loopVarDt(program: Program) = loopVar.inferType(program)
|
||||
}
|
||||
|
||||
class WhileLoop(var condition: Expression,
|
||||
var body: AnonymousScope,
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = true
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -861,36 +776,36 @@ class WhileLoop(var condition: Expression,
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
class ForeverLoop(var body: AnonymousScope, override val position: Position) : Statement() {
|
||||
class RepeatLoop(var iterations: Expression?, var body: AnonymousScope, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = true
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
iterations?.linkParents(this)
|
||||
body.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is AnonymousScope && node===body)
|
||||
body = replacement
|
||||
when {
|
||||
node===iterations -> iterations = replacement as Expression
|
||||
node===body -> body = replacement as AnonymousScope
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
class RepeatLoop(var body: AnonymousScope,
|
||||
class UntilLoop(var body: AnonymousScope,
|
||||
var untilCondition: Expression,
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = true
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -907,7 +822,6 @@ class RepeatLoop(var body: AnonymousScope,
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
@ -916,7 +830,6 @@ class WhenStatement(var condition: Expression,
|
||||
var choices: MutableList<WhenChoice>,
|
||||
override val position: Position): Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline: Boolean = true
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -928,7 +841,7 @@ class WhenStatement(var condition: Expression,
|
||||
if(node===condition)
|
||||
condition = replacement as Expression
|
||||
else {
|
||||
val idx = choices.indexOf(node)
|
||||
val idx = choices.withIndex().find { it.value===node }!!.index
|
||||
choices[idx] = replacement as WhenChoice
|
||||
}
|
||||
replacement.parent = this
|
||||
@ -952,7 +865,6 @@ class WhenStatement(var condition: Expression,
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
@ -978,7 +890,6 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
@ -988,7 +899,6 @@ class StructDecl(override val name: String,
|
||||
override val position: Position): Statement(), INameScope {
|
||||
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline: Boolean = true
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -997,7 +907,7 @@ class StructDecl(override val name: String,
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
val idx = statements.indexOfFirst { it===node }
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
@ -1006,7 +916,6 @@ class StructDecl(override val name: String,
|
||||
get() = this.statements.size
|
||||
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
fun nameOfFirstMember() = (statements.first() as VarDecl).name
|
||||
@ -1031,6 +940,5 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
@ -11,12 +11,14 @@ import prog8.ast.statements.*
|
||||
|
||||
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
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()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
@ -37,7 +39,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
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 target = AssignTarget(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)
|
||||
@ -45,7 +47,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
decls.map { IAstModification.InsertFirst(it, sub) } // move it up to the subroutine
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
@ -99,6 +101,6 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
}
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ fun compileProgram(filepath: Path,
|
||||
optimizeAst(programAst, errors)
|
||||
postprocessAst(programAst, errors, compilationOptions)
|
||||
|
||||
// printAst(programAst) // TODO
|
||||
// printAst(programAst)
|
||||
|
||||
if(writeAssembly)
|
||||
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
|
||||
@ -144,13 +144,12 @@ private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptio
|
||||
println("Processing...")
|
||||
programAst.checkIdentifiers(errors)
|
||||
errors.handle()
|
||||
programAst.makeForeverLoops()
|
||||
programAst.constantFold(errors)
|
||||
errors.handle()
|
||||
programAst.removeNopsFlattenAnonScopes()
|
||||
programAst.reorderStatements()
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
programAst.variousCleanups()
|
||||
programAst.checkValid(compilerOptions, errors)
|
||||
errors.handle()
|
||||
programAst.checkIdentifiers(errors)
|
||||
@ -180,11 +179,12 @@ private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerO
|
||||
errors.handle()
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
programAst.removeNopsFlattenAnonScopes()
|
||||
programAst.variousCleanups()
|
||||
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
|
||||
errors.handle()
|
||||
programAst.checkRecursion(errors) // check if there are recursive subroutine calls
|
||||
errors.handle()
|
||||
programAst.verifyFunctionArgTypes()
|
||||
}
|
||||
|
||||
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
|
||||
@ -194,7 +194,7 @@ private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir:
|
||||
programAst.processAstBeforeAsmGeneration(errors)
|
||||
errors.handle()
|
||||
|
||||
// printAst(programAst) // TODO
|
||||
// printAst(programAst)
|
||||
|
||||
val assembly = CompilationTarget.asmGenerator(
|
||||
programAst,
|
||||
|
@ -5,9 +5,6 @@ import prog8.compiler.CompilerException
|
||||
import prog8.compiler.Zeropage
|
||||
import prog8.compiler.ZeropageType
|
||||
import prog8.compiler.target.IMachineDefinition
|
||||
import java.awt.Color
|
||||
import java.awt.image.BufferedImage
|
||||
import javax.imageio.ImageIO
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.pow
|
||||
|
||||
@ -177,90 +174,4 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
return if (sign) -result else result
|
||||
}
|
||||
}
|
||||
|
||||
object Charset {
|
||||
private val normalImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-normal.png"))
|
||||
private val shiftedImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-shifted.png"))
|
||||
|
||||
private fun scanChars(img: BufferedImage): Array<BufferedImage> {
|
||||
|
||||
val transparent = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB)
|
||||
transparent.createGraphics().drawImage(img, 0, 0, null)
|
||||
|
||||
val black = Color(0, 0, 0).rgb
|
||||
val nopixel = Color(0, 0, 0, 0).rgb
|
||||
for (y in 0 until transparent.height) {
|
||||
for (x in 0 until transparent.width) {
|
||||
val col = transparent.getRGB(x, y)
|
||||
if (col == black)
|
||||
transparent.setRGB(x, y, nopixel)
|
||||
}
|
||||
}
|
||||
|
||||
val numColumns = transparent.width / 8
|
||||
val charImages = (0..255).map {
|
||||
val charX = it % numColumns
|
||||
val charY = it / numColumns
|
||||
transparent.getSubimage(charX * 8, charY * 8, 8, 8)
|
||||
}
|
||||
return charImages.toTypedArray()
|
||||
}
|
||||
|
||||
val normalChars = scanChars(normalImg)
|
||||
val shiftedChars = scanChars(shiftedImg)
|
||||
|
||||
private val coloredNormalChars = mutableMapOf<Short, Array<BufferedImage>>()
|
||||
|
||||
fun getColoredChar(screenCode: Short, color: Short): BufferedImage {
|
||||
val colorIdx = (color % colorPalette.size).toShort()
|
||||
val chars = coloredNormalChars[colorIdx]
|
||||
if (chars != null)
|
||||
return chars[screenCode.toInt()]
|
||||
|
||||
val coloredChars = mutableListOf<BufferedImage>()
|
||||
val transparent = Color(0, 0, 0, 0).rgb
|
||||
val rgb = colorPalette[colorIdx.toInt()].rgb
|
||||
for (c in normalChars) {
|
||||
val colored = c.copy()
|
||||
for (y in 0 until colored.height)
|
||||
for (x in 0 until colored.width) {
|
||||
if (colored.getRGB(x, y) != transparent) {
|
||||
colored.setRGB(x, y, rgb)
|
||||
}
|
||||
}
|
||||
coloredChars.add(colored)
|
||||
}
|
||||
coloredNormalChars[colorIdx] = coloredChars.toTypedArray()
|
||||
return coloredNormalChars.getValue(colorIdx)[screenCode.toInt()]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun BufferedImage.copy(): BufferedImage {
|
||||
val bcopy = BufferedImage(this.width, this.height, this.type)
|
||||
val g = bcopy.graphics
|
||||
g.drawImage(this, 0, 0, null)
|
||||
g.dispose()
|
||||
return bcopy
|
||||
}
|
||||
|
||||
val colorPalette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
|
||||
Color(0x000000), // 0 = black
|
||||
Color(0xFFFFFF), // 1 = white
|
||||
Color(0x813338), // 2 = red
|
||||
Color(0x75cec8), // 3 = cyan
|
||||
Color(0x8e3c97), // 4 = purple
|
||||
Color(0x56ac4d), // 5 = green
|
||||
Color(0x2e2c9b), // 6 = blue
|
||||
Color(0xedf171), // 7 = yellow
|
||||
Color(0x8e5029), // 8 = orange
|
||||
Color(0x553800), // 9 = brown
|
||||
Color(0xc46c71), // 10 = light red
|
||||
Color(0x4a4a4a), // 11 = dark grey
|
||||
Color(0x7b7b7b), // 12 = medium grey
|
||||
Color(0xa9ff9f), // 13 = light green
|
||||
Color(0x706deb), // 14 = light blue
|
||||
Color(0xb2b2b2) // 15 = light grey
|
||||
)
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.antlr.escape
|
||||
@ -183,7 +184,7 @@ internal class AsmGen(private val program: Program,
|
||||
blockLevelVarInits.getValue(block).forEach { decl ->
|
||||
val scopedFullName = decl.makeScopedName(decl.name).split('.')
|
||||
require(scopedFullName.first()==block.name)
|
||||
val target = AssignTarget(null, IdentifierReference(scopedFullName.drop(1), decl.position), null, null, decl.position)
|
||||
val target = AssignTarget(IdentifierReference(scopedFullName.drop(1), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, null, decl.value!!, decl.position)
|
||||
assign.linkParents(decl.parent)
|
||||
assignmentAsmGen.translate(assign)
|
||||
@ -561,19 +562,19 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun saveRegister(register: Register) {
|
||||
internal fun saveRegister(register: CpuRegister) {
|
||||
when(register) {
|
||||
Register.A -> out(" pha")
|
||||
Register.X -> out(" txa | pha")
|
||||
Register.Y -> out(" tya | pha")
|
||||
CpuRegister.A -> out(" pha")
|
||||
CpuRegister.X -> out(" txa | pha")
|
||||
CpuRegister.Y -> out(" tya | pha")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun restoreRegister(register: Register) {
|
||||
internal fun restoreRegister(register: CpuRegister) {
|
||||
when(register) {
|
||||
Register.A -> out(" pla")
|
||||
Register.X -> out(" pla | tax")
|
||||
Register.Y -> out(" pla | tay")
|
||||
CpuRegister.A -> out(" pla")
|
||||
CpuRegister.X -> out(" pla | tax")
|
||||
CpuRegister.Y -> out(" pla | tay")
|
||||
}
|
||||
}
|
||||
|
||||
@ -640,8 +641,8 @@ internal class AsmGen(private val program: Program,
|
||||
is Continue -> out(" jmp ${loopContinueLabels.peek()}")
|
||||
is Break -> out(" jmp ${loopEndLabels.peek()}")
|
||||
is WhileLoop -> translate(stmt)
|
||||
is ForeverLoop -> translate(stmt)
|
||||
is RepeatLoop -> translate(stmt)
|
||||
is UntilLoop -> translate(stmt)
|
||||
is WhenStatement -> translate(stmt)
|
||||
is BuiltinFunctionStatementPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore?")
|
||||
is AnonymousScope -> translate(stmt)
|
||||
@ -672,26 +673,114 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(stmt: ForeverLoop) {
|
||||
val foreverLabel = makeLabel("forever")
|
||||
val endLabel = makeLabel("foreverend")
|
||||
private fun translate(stmt: RepeatLoop) {
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val endLabel = makeLabel("repeatend")
|
||||
val counterLabel = makeLabel("repeatcounter")
|
||||
loopEndLabels.push(endLabel)
|
||||
loopContinueLabels.push(foreverLabel)
|
||||
out(foreverLabel)
|
||||
loopContinueLabels.push(repeatLabel)
|
||||
|
||||
when (stmt.iterations) {
|
||||
null -> {
|
||||
// endless loop
|
||||
out(repeatLabel)
|
||||
translate(stmt.body)
|
||||
out(" jmp $foreverLabel")
|
||||
out(" jmp $repeatLabel")
|
||||
out(endLabel)
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
val iterations = (stmt.iterations as NumericLiteralValue).number.toInt()
|
||||
if(iterations<0 || iterations > 65536)
|
||||
throw AssemblyError("invalid number of iterations")
|
||||
when {
|
||||
iterations == 0 -> {}
|
||||
iterations <= 255 -> {
|
||||
out(" lda #${iterations}")
|
||||
repeatByteCountInA(counterLabel, repeatLabel, endLabel, stmt.body)
|
||||
}
|
||||
else -> {
|
||||
out(" lda #<${iterations} | ldy #>${iterations}")
|
||||
repeatWordCountInAY(counterLabel, repeatLabel, endLabel, stmt.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val vardecl = (stmt.iterations as IdentifierReference).targetStatement(program.namespace) as VarDecl
|
||||
val name = asmIdentifierName(stmt.iterations as IdentifierReference)
|
||||
when(vardecl.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
out(" lda $name")
|
||||
repeatByteCountInA(counterLabel, repeatLabel, endLabel, stmt.body)
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
out(" lda $name | ldy $name+1")
|
||||
repeatWordCountInAY(counterLabel, repeatLabel, endLabel, stmt.body)
|
||||
}
|
||||
else -> throw AssemblyError("invalid loop variable datatype $vardecl")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
translateExpression(stmt.iterations!!)
|
||||
val dt = stmt.iterations!!.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
when (dt) {
|
||||
in ByteDatatypes -> {
|
||||
out(" inx | lda ${ESTACK_LO_HEX},x")
|
||||
repeatByteCountInA(counterLabel, repeatLabel, endLabel, stmt.body)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
out(" inx | lda ${ESTACK_LO_HEX},x | ldy ${ESTACK_HI_HEX},x")
|
||||
repeatWordCountInAY(counterLabel, repeatLabel, endLabel, stmt.body)
|
||||
}
|
||||
else -> throw AssemblyError("invalid loop expression datatype $dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loopEndLabels.pop()
|
||||
loopContinueLabels.pop()
|
||||
}
|
||||
|
||||
private fun repeatWordCountInAY(counterLabel: String, repeatLabel: String, endLabel: String, body: AnonymousScope) {
|
||||
// note: A/Y must have been loaded with the number of iterations already!
|
||||
out("""
|
||||
sta $counterLabel
|
||||
sty $counterLabel+1
|
||||
$repeatLabel lda $counterLabel
|
||||
bne +
|
||||
lda $counterLabel+1
|
||||
beq $endLabel
|
||||
+ lda $counterLabel
|
||||
bne +
|
||||
dec $counterLabel+1
|
||||
+ dec $counterLabel
|
||||
""")
|
||||
translate(body)
|
||||
out("""
|
||||
jmp $repeatLabel
|
||||
$counterLabel .word 0
|
||||
$endLabel""")
|
||||
}
|
||||
|
||||
private fun repeatByteCountInA(counterLabel: String, repeatLabel: String, endLabel: String, body: AnonymousScope) {
|
||||
// note: A must have been loaded with the number of iterations already!
|
||||
out("""
|
||||
sta $counterLabel
|
||||
$repeatLabel lda $counterLabel
|
||||
beq $endLabel
|
||||
dec $counterLabel""")
|
||||
translate(body)
|
||||
out("""
|
||||
jmp $repeatLabel
|
||||
$counterLabel .byte 0
|
||||
$endLabel""")
|
||||
}
|
||||
|
||||
private fun translate(stmt: WhileLoop) {
|
||||
val whileLabel = makeLabel("while")
|
||||
val endLabel = makeLabel("whileend")
|
||||
loopEndLabels.push(endLabel)
|
||||
loopContinueLabels.push(whileLabel)
|
||||
out(whileLabel)
|
||||
// TODO optimize for the simple cases, can we avoid stack use?
|
||||
expressionsAsmGen.translateExpression(stmt.condition)
|
||||
val conditionDt = stmt.condition.inferType(program)
|
||||
if(!conditionDt.isKnown)
|
||||
@ -714,13 +803,12 @@ internal class AsmGen(private val program: Program,
|
||||
loopContinueLabels.pop()
|
||||
}
|
||||
|
||||
private fun translate(stmt: RepeatLoop) {
|
||||
private fun translate(stmt: UntilLoop) {
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val endLabel = makeLabel("repeatend")
|
||||
loopEndLabels.push(endLabel)
|
||||
loopContinueLabels.push(repeatLabel)
|
||||
out(repeatLabel)
|
||||
// TODO optimize this for the simple cases, can we avoid stack use?
|
||||
translate(stmt.body)
|
||||
expressionsAsmGen.translateExpression(stmt.untilCondition)
|
||||
val conditionDt = stmt.untilCondition.inferType(program)
|
||||
@ -838,13 +926,16 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
inits.add(stmt)
|
||||
} else {
|
||||
val target = AssignTarget(null, IdentifierReference(listOf(stmt.name), stmt.position), null, null, stmt.position)
|
||||
val next = (stmt.parent as INameScope).nextSibling(stmt)
|
||||
if (next !is ForLoop || next.loopVar.nameInSource.single() != stmt.name) {
|
||||
val target = AssignTarget(IdentifierReference(listOf(stmt.name), stmt.position), null, null, stmt.position)
|
||||
val assign = Assignment(target, null, stmt.value!!, stmt.position)
|
||||
assign.linkParents(stmt.parent)
|
||||
translate(assign)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(stmt: Directive) {
|
||||
when(stmt.directive) {
|
||||
@ -903,13 +994,6 @@ internal class AsmGen(private val program: Program,
|
||||
internal fun translateArrayIndexIntoA(expr: ArrayIndexedExpression) {
|
||||
when (val index = expr.arrayspec.index) {
|
||||
is NumericLiteralValue -> throw AssemblyError("this should be optimized directly")
|
||||
is RegisterExpr -> {
|
||||
when (index.register) {
|
||||
Register.A -> {}
|
||||
Register.X -> out(" txa")
|
||||
Register.Y -> out(" tya")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val indexName = asmIdentifierName(index)
|
||||
out(" lda $indexName")
|
||||
@ -925,13 +1009,6 @@ 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")
|
||||
@ -974,9 +1051,12 @@ internal class AsmGen(private val program: Program,
|
||||
fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) =
|
||||
assignmentAsmGen.assignFromFloatVariable(target, variable)
|
||||
|
||||
fun assignFromRegister(target: AssignTarget, register: Register) =
|
||||
fun assignFromRegister(target: AssignTarget, register: CpuRegister) =
|
||||
assignmentAsmGen.assignFromRegister(target, register)
|
||||
|
||||
fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) =
|
||||
assignmentAsmGen.assignFromMemoryByte(target, address, identifier)
|
||||
|
||||
fun assignToRegister(reg: CpuRegister, value: Short?, identifier: IdentifierReference?) =
|
||||
assignmentAsmGen.assignToRegister(reg, value, identifier)
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,12 +2,8 @@ package prog8.compiler.target.c64.codegen
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ByteDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Register
|
||||
import prog8.ast.base.WordDatatypes
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
@ -176,13 +172,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr prog8_lib.ror2_mem_ub")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
when (what.register) {
|
||||
Register.A -> asmgen.out(" lsr a | bcc + | ora #\$80 |+ ")
|
||||
Register.X -> asmgen.out(" txa | lsr a | bcc + | ora #\$80 |+ tax ")
|
||||
Register.Y -> asmgen.out(" tya | lsr a | bcc + | ora #\$80 |+ tay ")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = asmgen.asmIdentifierName(what)
|
||||
asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable")
|
||||
@ -235,13 +224,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
""")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
when (what.register) {
|
||||
Register.A -> asmgen.out(" ror a")
|
||||
Register.X -> asmgen.out(" txa | ror a | tax")
|
||||
Register.Y -> asmgen.out(" tya | ror a | tay")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = asmgen.asmIdentifierName(what)
|
||||
asmgen.out(" ror $variable")
|
||||
@ -287,13 +269,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr prog8_lib.rol2_mem_ub")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
when (what.register) {
|
||||
Register.A -> asmgen.out(" cmp #\$80 | rol a ")
|
||||
Register.X -> asmgen.out(" txa | cmp #\$80 | rol a | tax")
|
||||
Register.Y -> asmgen.out(" tya | cmp #\$80 | rol a | tay")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = asmgen.asmIdentifierName(what)
|
||||
asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable")
|
||||
@ -346,13 +321,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
""")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
when (what.register) {
|
||||
Register.A -> asmgen.out(" rol a")
|
||||
Register.X -> asmgen.out(" txa | rol a | tax")
|
||||
Register.Y -> asmgen.out(" tya | rol a | tay")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = asmgen.asmIdentifierName(what)
|
||||
asmgen.out(" rol $variable")
|
||||
@ -384,13 +352,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is RegisterExpr -> {
|
||||
when (what.register) {
|
||||
Register.A -> asmgen.out(" lsr a")
|
||||
Register.X -> asmgen.out(" txa | lsr a | tax")
|
||||
Register.Y -> asmgen.out(" tya | lsr a | tay")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> asmgen.out(" lsr ${asmgen.asmIdentifierName(what)}")
|
||||
is DirectMemoryRead -> {
|
||||
if (what.addressExpression is NumericLiteralValue) {
|
||||
@ -468,13 +429,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
in ByteDatatypes -> {
|
||||
when (what) {
|
||||
is RegisterExpr -> {
|
||||
when (what.register) {
|
||||
Register.A -> asmgen.out(" asl a")
|
||||
Register.X -> asmgen.out(" txa | asl a | tax")
|
||||
Register.Y -> asmgen.out(" tya | asl a | tay")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> asmgen.out(" asl ${asmgen.asmIdentifierName(what)}")
|
||||
is DirectMemoryRead -> {
|
||||
if (what.addressExpression is NumericLiteralValue) {
|
||||
@ -581,32 +535,32 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
ldy $firstName
|
||||
lda $secondName
|
||||
sta $firstName
|
||||
tya
|
||||
sta $secondName
|
||||
sty $secondName
|
||||
ldy $firstName+1
|
||||
lda $secondName+1
|
||||
sta $firstName+1
|
||||
tya
|
||||
sta $secondName+1
|
||||
sty $secondName+1
|
||||
""")
|
||||
return
|
||||
}
|
||||
if(dt.istype(DataType.FLOAT)) {
|
||||
TODO("optimized case for swapping 2 float vars-- asm subroutine")
|
||||
asmgen.out("""
|
||||
lda #<$firstName
|
||||
sta ${C64Zeropage.SCRATCH_W1}
|
||||
lda #>$firstName
|
||||
sta ${C64Zeropage.SCRATCH_W1+1}
|
||||
lda #<$secondName
|
||||
sta ${C64Zeropage.SCRATCH_W2}
|
||||
lda #>$secondName
|
||||
sta ${C64Zeropage.SCRATCH_W2+1}
|
||||
jsr c64flt.swap_floats
|
||||
""")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TODO more optimized cases? for instance swapping elements of array vars?
|
||||
|
||||
// suboptimal code via the evaluation stack...
|
||||
asmgen.translateExpression(first)
|
||||
asmgen.translateExpression(second)
|
||||
// pop in reverse order
|
||||
val firstTarget = AssignTarget.fromExpr(first)
|
||||
val secondTarget = AssignTarget.fromExpr(second)
|
||||
asmgen.assignFromEvalResult(firstTarget)
|
||||
asmgen.assignFromEvalResult(secondTarget)
|
||||
// other types of swap() calls should have been replaced by a different statement sequence involving a temp variable
|
||||
throw AssemblyError("no asm generation for swap funccall $fcall")
|
||||
}
|
||||
|
||||
private fun funcAbs(fcall: IFunctionCall, func: FSignature) {
|
||||
|
@ -25,11 +25,9 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
is AddressOf -> translateExpression(expression)
|
||||
is DirectMemoryRead -> translateExpression(expression)
|
||||
is NumericLiteralValue -> translateExpression(expression)
|
||||
is RegisterExpr -> translateExpression(expression)
|
||||
is IdentifierReference -> translateExpression(expression)
|
||||
is FunctionCall -> translateExpression(expression)
|
||||
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array assignment")
|
||||
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened")
|
||||
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
|
||||
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
|
||||
}
|
||||
}
|
||||
@ -175,14 +173,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: RegisterExpr) {
|
||||
when(expr.register) {
|
||||
Register.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex")
|
||||
Register.X -> asmgen.out(" txa | sta $ESTACK_LO_HEX,x | dex")
|
||||
Register.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: IdentifierReference) {
|
||||
val varname = asmgen.asmIdentifierName(expr)
|
||||
when(expr.inferType(program).typeOrElse(DataType.STRUCT)) {
|
||||
@ -240,16 +230,26 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(amount<=2)
|
||||
repeat(amount) { asmgen.out(" lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
|
||||
var left = amount
|
||||
while(left>=7) {
|
||||
asmgen.out(" jsr math.shift_right_uw_7")
|
||||
left -= 7
|
||||
}
|
||||
if (left in 0..2)
|
||||
repeat(left) { asmgen.out(" lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
|
||||
else
|
||||
asmgen.out(" jsr math.shift_right_uw_$amount") // 3-7 (8+ is done via other optimizations)
|
||||
asmgen.out(" jsr math.shift_right_uw_$left")
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(amount<=2)
|
||||
repeat(amount) { asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
|
||||
var left = amount
|
||||
while(left>=7) {
|
||||
asmgen.out(" jsr math.shift_right_w_7")
|
||||
left -= 7
|
||||
}
|
||||
if (left in 0..2)
|
||||
repeat(left) { asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
|
||||
else
|
||||
asmgen.out(" jsr math.shift_right_w_$amount") // 3-7 (8+ is done via other optimizations)
|
||||
asmgen.out(" jsr math.shift_right_w_$left")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
@ -269,11 +269,15 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(amount<=2) {
|
||||
repeat(amount) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x") }
|
||||
} else {
|
||||
asmgen.out(" jsr math.shift_left_w_$amount") // 3-7 (8+ is done via other optimizations)
|
||||
var left=amount
|
||||
while(left>=7) {
|
||||
asmgen.out(" jsr math.shift_left_w_7")
|
||||
left -= 7
|
||||
}
|
||||
if (left in 0..2)
|
||||
repeat(left) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x") }
|
||||
else
|
||||
asmgen.out(" jsr math.shift_left_w_$left")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package prog8.compiler.target.c64.codegen
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Register
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.RangeExpr
|
||||
import prog8.ast.statements.AssignTarget
|
||||
@ -16,7 +15,7 @@ import prog8.compiler.toHex
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
// todo choose more efficient comparisons to avoid needless lda's
|
||||
// todo optimize common case step == 2 / -2
|
||||
// todo optimize common case when step == 2 or -2
|
||||
|
||||
|
||||
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
@ -37,7 +36,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
is IdentifierReference -> {
|
||||
translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference)
|
||||
}
|
||||
else -> throw AssemblyError("can't iterate over ${stmt.iterable}")
|
||||
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,28 +54,8 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
// bytes, step 1 or -1
|
||||
|
||||
val incdec = if(stepsize==1) "inc" else "dec"
|
||||
if (stmt.loopRegister != null) {
|
||||
// loop register over range
|
||||
if(stmt.loopRegister!= Register.A)
|
||||
throw AssemblyError("can only use A")
|
||||
asmgen.translateExpression(range.to)
|
||||
asmgen.translateExpression(range.from)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda ${ESTACK_LO_HEX},x
|
||||
sta $loopLabel+1
|
||||
$loopLabel lda #0 ; modified""")
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.out("""
|
||||
$continueLabel lda $loopLabel+1
|
||||
cmp $ESTACK_LO_PLUS1_HEX,x
|
||||
beq $endLabel
|
||||
$incdec $loopLabel+1
|
||||
jmp $loopLabel
|
||||
$endLabel inx""")
|
||||
} else {
|
||||
// loop over byte range via loopvar
|
||||
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
||||
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
||||
asmgen.translateExpression(range.to)
|
||||
asmgen.translateExpression(range.from)
|
||||
asmgen.out("""
|
||||
@ -93,46 +72,12 @@ $continueLabel lda $varname
|
||||
jmp $loopLabel
|
||||
$endLabel inx""")
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
// bytes, step >= 2 or <= -2
|
||||
|
||||
if (stmt.loopRegister != null) {
|
||||
// loop register over range
|
||||
if(stmt.loopRegister!= Register.A)
|
||||
throw AssemblyError("can only use A")
|
||||
asmgen.translateExpression(range.to)
|
||||
asmgen.translateExpression(range.from)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda ${ESTACK_LO_HEX},x
|
||||
sta $loopLabel+1
|
||||
$loopLabel lda #0 ; modified""")
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.out("""
|
||||
$continueLabel lda $loopLabel+1""")
|
||||
if(stepsize>0) {
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc #$stepsize
|
||||
sta $loopLabel+1
|
||||
cmp $ESTACK_LO_PLUS1_HEX,x
|
||||
bcc $loopLabel
|
||||
beq $loopLabel""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc #${stepsize.absoluteValue}
|
||||
sta $loopLabel+1
|
||||
cmp $ESTACK_LO_PLUS1_HEX,x
|
||||
bcs $loopLabel""")
|
||||
}
|
||||
asmgen.out("""
|
||||
$endLabel inx""")
|
||||
} else {
|
||||
// loop over byte range via loopvar
|
||||
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
||||
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
||||
asmgen.translateExpression(range.to)
|
||||
asmgen.translateExpression(range.from)
|
||||
asmgen.out("""
|
||||
@ -163,7 +108,6 @@ $continueLabel lda $varname""")
|
||||
$endLabel inx""")
|
||||
}
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||
when {
|
||||
|
||||
@ -171,8 +115,8 @@ $endLabel inx""")
|
||||
|
||||
stepsize == 1 || stepsize == -1 -> {
|
||||
asmgen.translateExpression(range.to)
|
||||
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
||||
val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position),
|
||||
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
||||
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position),
|
||||
null, range.from, range.position)
|
||||
assignLoopvar.linkParents(stmt)
|
||||
asmgen.translate(assignLoopvar)
|
||||
@ -207,8 +151,8 @@ $endLabel inx""")
|
||||
// (u)words, step >= 2
|
||||
|
||||
asmgen.translateExpression(range.to)
|
||||
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
||||
val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position),
|
||||
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
||||
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position),
|
||||
null, range.from, range.position)
|
||||
assignLoopvar.linkParents(stmt)
|
||||
asmgen.translate(assignLoopvar)
|
||||
@ -256,8 +200,8 @@ $endLabel inx""")
|
||||
|
||||
// (u)words, step <= -2
|
||||
asmgen.translateExpression(range.to)
|
||||
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
||||
val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position),
|
||||
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
||||
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position),
|
||||
null, range.from, range.position)
|
||||
assignLoopvar.linkParents(stmt)
|
||||
asmgen.translate(assignLoopvar)
|
||||
@ -319,8 +263,6 @@ $endLabel inx""")
|
||||
val decl = ident.targetVarDecl(program.namespace)!!
|
||||
when(iterableDt) {
|
||||
DataType.STR -> {
|
||||
if(stmt.loopRegister!=null && stmt.loopRegister!= Register.A)
|
||||
throw AssemblyError("can only use A")
|
||||
asmgen.out("""
|
||||
lda #<$iterableName
|
||||
ldy #>$iterableName
|
||||
@ -328,8 +270,7 @@ $endLabel inx""")
|
||||
sty $loopLabel+2
|
||||
$loopLabel lda ${65535.toHex()} ; modified
|
||||
beq $endLabel""")
|
||||
if(stmt.loopVar!=null)
|
||||
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar!!)}")
|
||||
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar)}")
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.out("""
|
||||
$continueLabel inc $loopLabel+1
|
||||
@ -339,10 +280,8 @@ $continueLabel inc $loopLabel+1
|
||||
$endLabel""")
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter in such cases
|
||||
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter var in such cases
|
||||
val length = decl.arraysize!!.size()!!
|
||||
if(stmt.loopRegister!=null && stmt.loopRegister!= Register.A)
|
||||
throw AssemblyError("can only use A")
|
||||
val counterLabel = asmgen.makeLabel("for_counter")
|
||||
val modifiedLabel = asmgen.makeLabel("for_modified")
|
||||
asmgen.out("""
|
||||
@ -353,8 +292,7 @@ $endLabel""")
|
||||
ldy #0
|
||||
$loopLabel sty $counterLabel
|
||||
$modifiedLabel lda ${65535.toHex()},y ; modified""")
|
||||
if(stmt.loopVar!=null)
|
||||
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar!!)}")
|
||||
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar)}")
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.out("""
|
||||
$continueLabel ldy $counterLabel
|
||||
@ -366,14 +304,12 @@ $counterLabel .byte 0
|
||||
$endLabel""")
|
||||
}
|
||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter in such cases
|
||||
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter var in such cases
|
||||
val length = decl.arraysize!!.size()!! * 2
|
||||
if(stmt.loopRegister!=null)
|
||||
throw AssemblyError("can't use register to loop over words")
|
||||
val counterLabel = asmgen.makeLabel("for_counter")
|
||||
val modifiedLabel = asmgen.makeLabel("for_modified")
|
||||
val modifiedLabel2 = asmgen.makeLabel("for_modified2")
|
||||
val loopvarName = asmgen.asmIdentifierName(stmt.loopVar!!)
|
||||
val loopvarName = asmgen.asmIdentifierName(stmt.loopVar)
|
||||
asmgen.out("""
|
||||
lda #<$iterableName
|
||||
ldy #>$iterableName
|
||||
@ -410,7 +346,7 @@ $endLabel""")
|
||||
}
|
||||
|
||||
private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) {
|
||||
// TODO: optimize loop code when the range is < 256 iterations, don't need a separate counter in such cases
|
||||
// TODO: optimize loop code when the range is < 256 iterations, don't need a separate counter var in such cases
|
||||
if (range.isEmpty())
|
||||
throw AssemblyError("empty range")
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
@ -421,89 +357,8 @@ $endLabel""")
|
||||
when(iterableDt) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||
val counterLabel = asmgen.makeLabel("for_counter")
|
||||
if(stmt.loopRegister!=null) {
|
||||
|
||||
// loop register over range
|
||||
|
||||
if(stmt.loopRegister!= Register.A)
|
||||
throw AssemblyError("can only use A")
|
||||
when {
|
||||
range.step==1 -> {
|
||||
// step = 1
|
||||
asmgen.out("""
|
||||
lda #${range.first}
|
||||
sta $loopLabel+1
|
||||
lda #${range.last-range.first+1 and 255}
|
||||
sta $counterLabel
|
||||
$loopLabel lda #0 ; modified""")
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.out("""
|
||||
$continueLabel dec $counterLabel
|
||||
beq $endLabel
|
||||
inc $loopLabel+1
|
||||
jmp $loopLabel
|
||||
$counterLabel .byte 0
|
||||
$endLabel""")
|
||||
}
|
||||
range.step==-1 -> {
|
||||
// step = -1
|
||||
asmgen.out("""
|
||||
lda #${range.first}
|
||||
sta $loopLabel+1
|
||||
lda #${range.first-range.last+1 and 255}
|
||||
sta $counterLabel
|
||||
$loopLabel lda #0 ; modified """)
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.out("""
|
||||
$continueLabel dec $counterLabel
|
||||
beq $endLabel
|
||||
dec $loopLabel+1
|
||||
jmp $loopLabel
|
||||
$counterLabel .byte 0
|
||||
$endLabel""")
|
||||
}
|
||||
range.step >= 2 -> {
|
||||
// step >= 2
|
||||
asmgen.out("""
|
||||
lda #${(range.last-range.first) / range.step + 1}
|
||||
sta $counterLabel
|
||||
lda #${range.first}
|
||||
$loopLabel pha""")
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.out("""
|
||||
$continueLabel pla
|
||||
dec $counterLabel
|
||||
beq $endLabel
|
||||
clc
|
||||
adc #${range.step}
|
||||
jmp $loopLabel
|
||||
$counterLabel .byte 0
|
||||
$endLabel""")
|
||||
}
|
||||
else -> {
|
||||
// step <= -2
|
||||
asmgen.out("""
|
||||
lda #${(range.first-range.last) / range.step.absoluteValue + 1}
|
||||
sta $counterLabel
|
||||
lda #${range.first}
|
||||
$loopLabel pha""")
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.out("""
|
||||
$continueLabel pla
|
||||
dec $counterLabel
|
||||
beq $endLabel
|
||||
sec
|
||||
sbc #${range.step.absoluteValue}
|
||||
jmp $loopLabel
|
||||
$counterLabel .byte 0
|
||||
$endLabel""")
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// loop over byte range via loopvar
|
||||
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
||||
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
||||
when {
|
||||
range.step==1 -> {
|
||||
// step = 1
|
||||
@ -581,10 +436,9 @@ $endLabel""")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||
// loop over word range via loopvar
|
||||
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
||||
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
||||
when {
|
||||
range.step == 1 -> {
|
||||
// word, step = 1
|
||||
|
@ -19,14 +19,42 @@ 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}")
|
||||
val saveX = Register.X in sub.asmClobbers || sub.regXasResult()
|
||||
val saveX = CpuRegister.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)
|
||||
if(stmt.args.isNotEmpty()) {
|
||||
if(sub.asmParameterRegisters.isEmpty()) {
|
||||
// via variables
|
||||
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
|
||||
translateFuncArguments(arg.first, arg.second, sub)
|
||||
argumentViaVariable(sub, arg.first, arg.second)
|
||||
}
|
||||
} else {
|
||||
// via registers
|
||||
if(sub.parameters.size==1) {
|
||||
// just a single parameter, no risk of clobbering registers
|
||||
argumentViaRegister(sub, sub.parameters.withIndex().single(), stmt.args[0])
|
||||
} else {
|
||||
// multiple register arguments, risk of register clobbering.
|
||||
// evaluate arguments onto the stack, and load the registers from the evaluated values on the stack.
|
||||
when {
|
||||
stmt.args.all {it is AddressOf ||
|
||||
it is NumericLiteralValue ||
|
||||
it is StringLiteralValue ||
|
||||
it is ArrayLiteralValue ||
|
||||
it is IdentifierReference} -> {
|
||||
// no risk of clobbering for these simple argument types. Optimize the register loading.
|
||||
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
|
||||
argumentViaRegister(sub, arg.first, arg.second)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Risk of clobbering due to complex expression args. Work via the stack.
|
||||
argsViaStackEvaluation(stmt, sub)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
asmgen.out(" jsr $subName")
|
||||
@ -35,18 +63,50 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
|
||||
}
|
||||
|
||||
private fun translateFuncArguments(parameter: IndexedValue<SubroutineParameter>, value: Expression, sub: Subroutine) {
|
||||
val sourceIDt = value.inferType(program)
|
||||
if(!sourceIDt.isKnown)
|
||||
throw AssemblyError("arg type unknown")
|
||||
val sourceDt = sourceIDt.typeOrElse(DataType.STRUCT)
|
||||
if(!argumentTypeCompatible(sourceDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
if(sub.asmParameterRegisters.isEmpty()) {
|
||||
private fun argsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
|
||||
for (arg in stmt.args.reversed())
|
||||
asmgen.translateExpression(arg)
|
||||
for (regparam in sub.asmParameterRegisters) {
|
||||
when (regparam.registerOrPair) {
|
||||
RegisterOrPair.A -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
|
||||
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
|
||||
RegisterOrPair.Y -> asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
|
||||
RegisterOrPair.AX -> throw AssemblyError("can't pop into X register - use a variable instead")
|
||||
RegisterOrPair.AY -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
|
||||
RegisterOrPair.XY -> throw AssemblyError("can't pop into X register - use a variable instead")
|
||||
null -> {
|
||||
}
|
||||
}
|
||||
when (regparam.statusflag) {
|
||||
Statusflag.Pc -> asmgen.out("""
|
||||
inx
|
||||
pha
|
||||
lda $ESTACK_LO_HEX,x
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+ pla
|
||||
""")
|
||||
null -> {
|
||||
}
|
||||
else -> throw AssemblyError("can only use Carry as status flag parameter")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||
// pass parameter via a regular variable (not via registers)
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("arg type unknown")
|
||||
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
||||
if(!argumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
val paramVar = parameter.value
|
||||
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
|
||||
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
|
||||
val target = AssignTarget(IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
|
||||
target.linkParents(value.parent)
|
||||
when (value) {
|
||||
is NumericLiteralValue -> {
|
||||
@ -69,9 +129,6 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
else -> throw AssemblyError("weird parameter datatype")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
asmgen.assignFromRegister(target, value.register)
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
when(value.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
@ -84,7 +141,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
else -> {
|
||||
asmgen.translateExpression(value.addressExpression)
|
||||
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
|
||||
asmgen.assignFromRegister(target, Register.A)
|
||||
asmgen.assignFromRegister(target, CpuRegister.A)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -93,8 +150,17 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
asmgen.assignFromEvalResult(target)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// pass parameter via a register parameter
|
||||
}
|
||||
|
||||
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||
// pass argument via a register parameter
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("arg type unknown")
|
||||
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
||||
if(!argumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
val paramRegister = sub.asmParameterRegisters[parameter.index]
|
||||
val statusflag = paramRegister.statusflag
|
||||
val register = paramRegister.registerOrPair
|
||||
@ -123,32 +189,19 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
bcs ++
|
||||
+ clc
|
||||
+
|
||||
""")
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
when(value.register) {
|
||||
Register.A -> asmgen.out(" cmp #0")
|
||||
Register.X -> asmgen.out(" txa")
|
||||
Register.Y -> asmgen.out(" tya")
|
||||
}
|
||||
asmgen.out("""
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+
|
||||
""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(value)
|
||||
asmgen.out("""
|
||||
inx
|
||||
pha
|
||||
lda $ESTACK_LO_HEX,x
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+
|
||||
+ pla
|
||||
""")
|
||||
}
|
||||
}
|
||||
@ -158,14 +211,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
register!=null && register.name.length==1 -> {
|
||||
when (value) {
|
||||
is NumericLiteralValue -> {
|
||||
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
|
||||
target.linkParents(value.parent)
|
||||
asmgen.assignFromByteConstant(target, value.number.toShort())
|
||||
asmgen.assignToRegister(CpuRegister.valueOf(register.name), value.number.toShort(), null)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
|
||||
target.linkParents(value.parent)
|
||||
asmgen.assignFromByteVariable(target, value)
|
||||
asmgen.assignToRegister(CpuRegister.valueOf(register.name), null, value)
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(value)
|
||||
@ -203,7 +252,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val sourceName = asmgen.asmIdentifierName(value)
|
||||
if(sourceDt in PassByReferenceDatatypes) {
|
||||
if(valueDt in PassByReferenceDatatypes) {
|
||||
when (register) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
|
||||
@ -230,7 +279,6 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
|
||||
if(argType isAssignableTo paramType)
|
||||
|
@ -4,7 +4,6 @@ import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.RegisterExpr
|
||||
import prog8.ast.statements.PostIncrDecr
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
@ -17,28 +16,10 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
val targetIdent = stmt.target.identifier
|
||||
val targetMemory = stmt.target.memoryAddress
|
||||
val targetArrayIdx = stmt.target.arrayindexed
|
||||
val targetRegister = stmt.target.register
|
||||
when {
|
||||
targetRegister!=null -> {
|
||||
when(targetRegister) {
|
||||
Register.A -> {
|
||||
if(incr)
|
||||
asmgen.out(" clc | adc #1 ")
|
||||
else
|
||||
asmgen.out(" sec | sbc #1 ")
|
||||
}
|
||||
Register.X -> {
|
||||
if(incr) asmgen.out(" inx") else asmgen.out(" dex")
|
||||
}
|
||||
Register.Y -> {
|
||||
if(incr) asmgen.out(" iny") else asmgen.out(" dey")
|
||||
}
|
||||
}
|
||||
}
|
||||
targetIdent!=null -> {
|
||||
val what = asmgen.asmIdentifierName(targetIdent)
|
||||
val dt = stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)
|
||||
when (dt) {
|
||||
when (stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)) {
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
@ -103,10 +84,6 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
else -> throw AssemblyError("need numeric type")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||
|
@ -87,7 +87,20 @@ val BuiltinFunctions = mapOf(
|
||||
FParam("address", IterableDatatypes + DataType.UWORD),
|
||||
FParam("numwords", setOf(DataType.UWORD)),
|
||||
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
|
||||
"strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen)
|
||||
"strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
|
||||
"substr" to FSignature(false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("start", setOf(DataType.UBYTE)),
|
||||
FParam("length", setOf(DataType.UBYTE))), null),
|
||||
"leftstr" to FSignature(false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("length", setOf(DataType.UBYTE))), null),
|
||||
"rightstr" to FSignature(false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("length", setOf(DataType.UBYTE))), null)
|
||||
)
|
||||
|
||||
fun builtinMax(array: List<Number>): Number = array.maxBy { it.toDouble() }!!
|
||||
@ -172,6 +185,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
|
||||
|
||||
|
||||
class NotConstArgumentException: AstException("not a const argument to a built-in function")
|
||||
class CannotEvaluateException(func:String, msg: String): FatalAstException("cannot evaluate built-in function $func: $msg")
|
||||
|
||||
|
||||
private fun oneDoubleArg(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
|
||||
@ -252,17 +266,22 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
|
||||
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
|
||||
if(args[0] !is IdentifierReference)
|
||||
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
|
||||
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)!!
|
||||
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)
|
||||
?: throw CannotEvaluateException("len", "no target vardecl")
|
||||
|
||||
return when(target.datatype) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
arraySize = target.arraysize!!.size()!!
|
||||
arraySize = target.arraysize?.size()
|
||||
if(arraySize==null)
|
||||
throw CannotEvaluateException("len", "arraysize unknown")
|
||||
if(arraySize>256)
|
||||
throw CompilerException("array length exceeds byte limit ${target.position}")
|
||||
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
arraySize = target.arraysize!!.size()!!
|
||||
arraySize = target.arraysize?.size()
|
||||
if(arraySize==null)
|
||||
throw CannotEvaluateException("len", "arraysize unknown")
|
||||
if(arraySize>256)
|
||||
throw CompilerException("array length exceeds byte limit ${target.position}")
|
||||
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
||||
|
@ -14,6 +14,7 @@ import prog8.ast.statements.PostIncrDecr
|
||||
internal class AssignmentTransformer(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||
|
||||
var optimizationsDone: Int = 0
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
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
|
||||
@ -27,7 +28,7 @@ internal class AssignmentTransformer(val program: Program, val errors: ErrorRepo
|
||||
assignment.aug_op = binExpr.operator + "="
|
||||
assignment.value.parent = assignment
|
||||
optimizationsDone++
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
assignment.aug_op = "setvalue"
|
||||
@ -151,6 +152,6 @@ internal class AssignmentTransformer(val program: Program, val errors: ErrorRepo
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
@ -24,10 +24,10 @@ private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexO
|
||||
|
||||
class CallGraph(private val program: Program) : IAstVisitor {
|
||||
|
||||
val modulesImporting = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val modulesImportedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
|
||||
val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
||||
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val calls = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
|
||||
val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
||||
|
||||
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
|
||||
val usedSymbols = mutableSetOf<Statement>()
|
||||
@ -55,17 +55,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
it.importedBy.clear()
|
||||
it.imports.clear()
|
||||
|
||||
it.importedBy.addAll(modulesImportedBy.getValue(it))
|
||||
it.imports.addAll(modulesImporting.getValue(it))
|
||||
|
||||
forAllSubroutines(it) { sub ->
|
||||
sub.calledBy.clear()
|
||||
sub.calls.clear()
|
||||
|
||||
sub.calledBy.addAll(subroutinesCalledBy.getValue(sub))
|
||||
sub.calls.addAll(subroutinesCalling.getValue(sub))
|
||||
}
|
||||
|
||||
it.importedBy.addAll(importedBy.getValue(it))
|
||||
it.imports.addAll(imports.getValue(it))
|
||||
}
|
||||
|
||||
val rootmodule = program.modules.first()
|
||||
@ -85,8 +76,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
val thisModule = directive.definingModule()
|
||||
if (directive.directive == "%import") {
|
||||
val importedModule: Module = program.modules.single { it.name == directive.args[0].name }
|
||||
modulesImporting[thisModule] = modulesImporting.getValue(thisModule).plus(importedModule)
|
||||
modulesImportedBy[importedModule] = modulesImportedBy.getValue(importedModule).plus(thisModule)
|
||||
imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
|
||||
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
|
||||
} else if (directive.directive == "%asminclude") {
|
||||
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
|
||||
val scope = directive.definingScope()
|
||||
@ -141,8 +132,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
val otherSub = functionCall.target.targetSubroutine(program.namespace)
|
||||
if (otherSub != null) {
|
||||
functionCall.definingSubroutine()?.let { thisSub ->
|
||||
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
|
||||
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCall)
|
||||
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCall)
|
||||
}
|
||||
}
|
||||
super.visit(functionCall)
|
||||
@ -152,8 +143,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
val otherSub = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||
if (otherSub != null) {
|
||||
functionCallStatement.definingSubroutine()?.let { thisSub ->
|
||||
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
|
||||
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCallStatement)
|
||||
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCallStatement)
|
||||
}
|
||||
}
|
||||
super.visit(functionCallStatement)
|
||||
@ -163,8 +154,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
val otherSub = jump.identifier?.targetSubroutine(program.namespace)
|
||||
if (otherSub != null) {
|
||||
jump.definingSubroutine()?.let { thisSub ->
|
||||
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
|
||||
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(jump)
|
||||
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub).plus(jump)
|
||||
}
|
||||
}
|
||||
super.visit(jump)
|
||||
@ -190,14 +181,14 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
|
||||
val node = program.namespace.lookup(jumptarget.split('.'), context)
|
||||
if (node is Subroutine) {
|
||||
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
|
||||
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
|
||||
calls[scope] = calls.getValue(scope).plus(node)
|
||||
calledBy[node] = calledBy.getValue(node).plus(context)
|
||||
} else if (jumptarget.contains('.')) {
|
||||
// maybe only the first part already refers to a subroutine
|
||||
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
|
||||
if (node2 is Subroutine) {
|
||||
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node2)
|
||||
subroutinesCalledBy[node2] = subroutinesCalledBy.getValue(node2).plus(context)
|
||||
calls[scope] = calls.getValue(scope).plus(node2)
|
||||
calledBy[node2] = calledBy.getValue(node2).plus(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -209,8 +200,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
if (target.contains('.')) {
|
||||
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
|
||||
if (node is Subroutine) {
|
||||
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
|
||||
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
|
||||
calls[scope] = calls.getValue(scope).plus(node)
|
||||
calledBy[node] = calledBy.getValue(node).plus(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,46 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
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
|
||||
// First thing to do is replace all constant identifiers with their actual value,
|
||||
// and the array var initializer values and sizes.
|
||||
// This is needed because further constant optimizations depend on those.
|
||||
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||
// replace identifiers that refer to const value, with the value itself
|
||||
// if it's a simple type and if it's not a left hand side variable
|
||||
if(identifier.parent is AssignTarget)
|
||||
return noModifications
|
||||
var forloop = identifier.parent as? ForLoop
|
||||
if(forloop==null)
|
||||
forloop = identifier.parent.parent as? ForLoop
|
||||
if(forloop!=null && identifier===forloop.loopVar)
|
||||
return noModifications
|
||||
|
||||
val cval = identifier.constValue(program) ?: return noModifications
|
||||
return when (cval.type) {
|
||||
in NumericDatatypes -> listOf(IAstModification.ReplaceNode(identifier, NumericLiteralValue(cval.type, cval.number, identifier.position), identifier.parent))
|
||||
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
|
||||
else -> noModifications
|
||||
}
|
||||
}
|
||||
|
||||
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
// TODO: use call graph for this?
|
||||
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
|
||||
errors.err("recursive var declaration", decl.position)
|
||||
return decl
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
|
||||
@ -28,15 +49,20 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
|
||||
val arrayval = decl.value as? ArrayLiteralValue
|
||||
if(arrayval!=null) {
|
||||
decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position), decl.position)
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SetExpression(
|
||||
{ decl.arraysize = ArrayIndex(it, decl.position) },
|
||||
NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position),
|
||||
decl
|
||||
))
|
||||
}
|
||||
}
|
||||
else if(decl.arraysize?.size()==null) {
|
||||
val size = decl.arraysize!!.index.accept(this)
|
||||
if(size is NumericLiteralValue) {
|
||||
decl.arraysize = ArrayIndex(size, decl.position)
|
||||
optimizationsDone++
|
||||
val size = decl.arraysize!!.index.constValue(program)
|
||||
if(size!=null) {
|
||||
return listOf(IAstModification.SetExpression(
|
||||
{ decl.arraysize = ArrayIndex(it, decl.position) },
|
||||
size, decl
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,9 +73,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
if (litval!=null && litval.type in IntegerDatatypes) {
|
||||
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
|
||||
decl.value = newValue
|
||||
optimizationsDone++
|
||||
return super.visit(decl)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
@ -63,23 +87,21 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
|
||||
if(eltType in ByteDatatypes) {
|
||||
decl.value = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
val newValue = if(eltType in ByteDatatypes) {
|
||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
} else {
|
||||
decl.value = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
decl.value!!.linkParents(decl)
|
||||
optimizationsDone++
|
||||
return super.visit(decl)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
if(numericLv!=null && numericLv.type==DataType.FLOAT)
|
||||
errors.err("arraysize requires only integers here", numericLv.position)
|
||||
val size = decl.arraysize?.size() ?: return decl
|
||||
val size = decl.arraysize?.size() ?: return noModifications
|
||||
if (rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
||||
val fillvalue = numericLv.number.toInt()
|
||||
@ -105,19 +127,27 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayElementTypes.getValue(decl.datatype), it, numericLv.position) as Expression}.toTypedArray()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
||||
decl.value = refValue
|
||||
refValue.parent=decl
|
||||
optimizationsDone++
|
||||
return super.visit(decl)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val size = decl.arraysize?.size() ?: return decl
|
||||
val size = decl.arraysize?.size() ?: return noModifications
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
if(litval==null) {
|
||||
// there's no initialization value, but the size is known, so we're ok.
|
||||
return super.visit(decl)
|
||||
} else {
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array of floats
|
||||
val declArraySize = decl.arraysize?.size()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
if(rangeExpr==null && litval!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = litval.number.toDouble()
|
||||
if (fillvalue < CompilationTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.machine.FLOAT_MAX_POSITIVE)
|
||||
@ -126,10 +156,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position)
|
||||
decl.value = refValue
|
||||
refValue.parent=decl
|
||||
optimizationsDone++
|
||||
return super.visit(decl)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -144,135 +171,69 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
if(declValue!=null && decl.type==VarDeclType.VAR
|
||||
&& declValue is NumericLiteralValue && !declValue.inferType(program).istype(decl.datatype)) {
|
||||
// cast the numeric literal to the appropriate datatype of the variable
|
||||
decl.value = declValue.cast(decl.datatype)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, declValue.cast(decl.datatype), decl))
|
||||
}
|
||||
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
/**
|
||||
* replace identifiers that refer to const value, with the value itself (if it's a simple type)
|
||||
*/
|
||||
override fun visit(identifier: IdentifierReference): Expression {
|
||||
// don't replace when it's an assignment target or loop variable
|
||||
if(identifier.parent is AssignTarget)
|
||||
return identifier
|
||||
var forloop = identifier.parent as? ForLoop
|
||||
if(forloop==null)
|
||||
forloop = identifier.parent.parent as? ForLoop
|
||||
if(forloop!=null && identifier===forloop.loopVar)
|
||||
return identifier
|
||||
|
||||
val cval = identifier.constValue(program) ?: return identifier
|
||||
return when (cval.type) {
|
||||
in NumericDatatypes -> {
|
||||
val copy = NumericLiteralValue(cval.type, cval.number, identifier.position)
|
||||
copy.parent = identifier.parent
|
||||
copy
|
||||
}
|
||||
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
|
||||
else -> identifier
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall): Expression {
|
||||
super.visit(functionCall)
|
||||
typeCastConstArguments(functionCall)
|
||||
return functionCall.constValue(program) ?: functionCall
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||
super.visit(functionCallStatement)
|
||||
typeCastConstArguments(functionCallStatement)
|
||||
return functionCallStatement
|
||||
}
|
||||
internal class ConstantFoldingOptimizer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
private fun typeCastConstArguments(functionCall: IFunctionCall) {
|
||||
if(functionCall.target.nameInSource.size==1) {
|
||||
val builtinFunction = BuiltinFunctions[functionCall.target.nameInSource.single()]
|
||||
if(builtinFunction!=null) {
|
||||
// match the arguments of a builtin function signature.
|
||||
for(arg in functionCall.args.withIndex().zip(builtinFunction.parameters)) {
|
||||
val possibleDts = arg.second.possibleDatatypes
|
||||
val argConst = arg.first.value.constValue(program)
|
||||
if(argConst!=null && argConst.type !in possibleDts) {
|
||||
val convertedValue = argConst.cast(possibleDts.first())
|
||||
functionCall.args[arg.first.index] = convertedValue
|
||||
optimizationsDone++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
// match the arguments of a subroutine.
|
||||
val subroutine = functionCall.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
// if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter
|
||||
for(arg in functionCall.args.withIndex().zip(subroutine.parameters)) {
|
||||
val expectedDt = arg.second.type
|
||||
val argConst = arg.first.value.constValue(program)
|
||||
if(argConst!=null && argConst.type!=expectedDt) {
|
||||
val convertedValue = argConst.cast(expectedDt)
|
||||
functionCall.args[arg.first.index] = convertedValue
|
||||
optimizationsDone++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(memread: DirectMemoryRead): Expression {
|
||||
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
// @( &thing ) --> thing
|
||||
val addrOf = memread.addressExpression as? AddressOf
|
||||
if(addrOf!=null)
|
||||
return super.visit(addrOf.identifier)
|
||||
return super.visit(memread)
|
||||
return if(addrOf!=null)
|
||||
listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to accept a unary prefix expression.
|
||||
* Compile-time constant sub expressions will be evaluated on the spot.
|
||||
* For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
|
||||
*/
|
||||
override fun visit(expr: PrefixExpression): Expression {
|
||||
val prefixExpr=super.visit(expr)
|
||||
if(prefixExpr !is PrefixExpression)
|
||||
return prefixExpr
|
||||
|
||||
val subexpr = prefixExpr.expression
|
||||
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
|
||||
// Try to turn a unary prefix expression into a single constant value.
|
||||
// Compile-time constant sub expressions will be evaluated on the spot.
|
||||
// For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
|
||||
val subexpr = expr.expression
|
||||
if (subexpr is NumericLiteralValue) {
|
||||
// accept prefixed literal values (such as -3, not true)
|
||||
return when (prefixExpr.operator) {
|
||||
"+" -> subexpr
|
||||
return when (expr.operator) {
|
||||
"+" -> listOf(IAstModification.ReplaceNode(expr, subexpr, parent))
|
||||
"-" -> when (subexpr.type) {
|
||||
in IntegerDatatypes -> {
|
||||
optimizationsDone++
|
||||
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position)
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
optimizationsDone++
|
||||
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position)
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
|
||||
}
|
||||
"~" -> when (subexpr.type) {
|
||||
in IntegerDatatypes -> {
|
||||
optimizationsDone++
|
||||
NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position)
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
|
||||
}
|
||||
"not" -> {
|
||||
optimizationsDone++
|
||||
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position)
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError(prefixExpr.operator, subexpr.position)
|
||||
else -> throw ExpressionError(expr.operator, subexpr.position)
|
||||
}
|
||||
}
|
||||
return prefixExpr
|
||||
return noModifications
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to accept a binary expression.
|
||||
* Try to constfold a binary expression.
|
||||
* Compile-time constant sub expressions will be evaluated on the spot.
|
||||
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
|
||||
*
|
||||
@ -288,13 +249,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
* (X / c1) * c2 -> X / (c2/c1)
|
||||
* (X + c1) - c2 -> X + (c1-c2)
|
||||
*/
|
||||
override fun visit(expr: BinaryExpression): Expression {
|
||||
super.visit(expr)
|
||||
|
||||
if(expr.left is StringLiteralValue || expr.left is ArrayLiteralValue
|
||||
|| expr.right is StringLiteralValue || expr.right is ArrayLiteralValue)
|
||||
throw FatalAstException("binexpr with reference litval instead of numeric")
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
val leftconst = expr.left.constValue(program)
|
||||
val rightconst = expr.right.constValue(program)
|
||||
|
||||
@ -308,218 +263,62 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
val subrightconst = subExpr.right.constValue(program)
|
||||
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) {
|
||||
// try reordering.
|
||||
return groupTwoConstsTogether(expr, subExpr,
|
||||
val change = groupTwoConstsTogether(expr, subExpr,
|
||||
leftconst != null, rightconst != null,
|
||||
subleftconst != null, subrightconst != null)
|
||||
return change?.let { listOf(it) } ?: noModifications
|
||||
}
|
||||
}
|
||||
|
||||
// const fold when both operands are a const
|
||||
return when {
|
||||
leftconst != null && rightconst != null -> {
|
||||
optimizationsDone++
|
||||
if(leftconst != null && rightconst != null) {
|
||||
val evaluator = ConstExprEvaluator()
|
||||
evaluator.evaluate(leftconst, expr.operator, rightconst)
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
evaluator.evaluate(leftconst, expr.operator, rightconst),
|
||||
parent
|
||||
))
|
||||
}
|
||||
|
||||
else -> expr
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
// because constant folding can result in arrays that are now suddenly capable
|
||||
// of telling the type of all their elements (for instance, when they contained -2 which
|
||||
// was a prefix expression earlier), we recalculate the array's datatype.
|
||||
if(array.type.isKnown)
|
||||
return noModifications
|
||||
|
||||
// if the array literalvalue is inside an array vardecl, take the type from that
|
||||
// otherwise infer it from the elements of the array
|
||||
val vardeclType = (array.parent as? VarDecl)?.datatype
|
||||
if(vardeclType!=null) {
|
||||
val newArray = array.cast(vardeclType)
|
||||
if (newArray != null && newArray != array)
|
||||
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
|
||||
} else {
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if (arrayDt.isKnown) {
|
||||
val newArray = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||
if (newArray != null && newArray != array)
|
||||
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
|
||||
}
|
||||
}
|
||||
|
||||
private fun groupTwoConstsTogether(expr: BinaryExpression,
|
||||
subExpr: BinaryExpression,
|
||||
leftIsConst: Boolean,
|
||||
rightIsConst: Boolean,
|
||||
subleftIsConst: Boolean,
|
||||
subrightIsConst: Boolean): Expression
|
||||
{
|
||||
// todo: this implements only a small set of possible reorderings at this time
|
||||
if(expr.operator==subExpr.operator) {
|
||||
// both operators are the isSameAs.
|
||||
// If + or *, we can simply swap the const of expr and Var in subexpr.
|
||||
if(expr.operator=="+" || expr.operator=="*") {
|
||||
if(leftIsConst) {
|
||||
if(subleftIsConst)
|
||||
expr.left = subExpr.right.also { subExpr.right = expr.left }
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
// the args of a fuction are constfolded via recursion already.
|
||||
val constvalue = functionCall.constValue(program)
|
||||
return if(constvalue!=null)
|
||||
listOf(IAstModification.ReplaceNode(functionCall, constvalue, parent))
|
||||
else
|
||||
expr.left = subExpr.left.also { subExpr.left = expr.left }
|
||||
} else {
|
||||
if(subleftIsConst)
|
||||
expr.right = subExpr.right.also {subExpr.right = expr.right }
|
||||
else
|
||||
expr.right = subExpr.left.also { subExpr.left = expr.right }
|
||||
}
|
||||
optimizationsDone++
|
||||
return expr
|
||||
noModifications
|
||||
}
|
||||
|
||||
// If - or /, we simetimes must reorder more, and flip operators (- -> +, / -> *)
|
||||
if(expr.operator=="-" || expr.operator=="/") {
|
||||
optimizationsDone++
|
||||
if(leftIsConst) {
|
||||
return if(subleftIsConst) {
|
||||
val tmp = subExpr.right
|
||||
subExpr.right = subExpr.left
|
||||
subExpr.left = expr.left
|
||||
expr.left = tmp
|
||||
expr.operator = if(expr.operator=="-") "+" else "*"
|
||||
expr
|
||||
} else
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
||||
expr.operator, subExpr.left, expr.position)
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
expr.right = subExpr.right.also { subExpr.right = expr.right }
|
||||
expr
|
||||
} else
|
||||
BinaryExpression(
|
||||
subExpr.left, expr.operator,
|
||||
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
||||
expr.position)
|
||||
}
|
||||
}
|
||||
return expr
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if(expr.operator=="/" && subExpr.operator=="*") {
|
||||
optimizationsDone++
|
||||
if(leftIsConst) {
|
||||
return if(subleftIsConst) {
|
||||
// C1/(C2*V) -> (C1/C2)/V
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "/", subExpr.left, subExpr.position),
|
||||
"/",
|
||||
subExpr.right, expr.position)
|
||||
} else {
|
||||
// C1/(V*C2) -> (C1/C2)/V
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
|
||||
"/",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
// (C1*V)/C2 -> (C1/C2)*V
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.left, "/", expr.right, subExpr.position),
|
||||
"*",
|
||||
subExpr.right, expr.position)
|
||||
} else {
|
||||
// (V*C1)/C2 -> (C1/C2)*V
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.right, "/", expr.right, subExpr.position),
|
||||
"*",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="*" && subExpr.operator=="/") {
|
||||
optimizationsDone++
|
||||
if(leftIsConst) {
|
||||
return if(subleftIsConst) {
|
||||
// C1*(C2/V) -> (C1*C2)/V
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "*", subExpr.left, subExpr.position),
|
||||
"/",
|
||||
subExpr.right, expr.position)
|
||||
} else {
|
||||
// C1*(V/C2) -> (C1/C2)*V
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
|
||||
"*",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
// (C1/V)*C2 -> (C1*C2)/V
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.left, "*", expr.right, subExpr.position),
|
||||
"/",
|
||||
subExpr.right, expr.position)
|
||||
} else {
|
||||
// (V/C1)*C2 -> (C1/C2)*V
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.right, "/", subExpr.right, subExpr.position),
|
||||
"*",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="+" && subExpr.operator=="-") {
|
||||
optimizationsDone++
|
||||
if(leftIsConst){
|
||||
return if(subleftIsConst){
|
||||
// c1+(c2-v) -> (c1+c2)-v
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "+", subExpr.left, subExpr.position),
|
||||
"-",
|
||||
subExpr.right, expr.position)
|
||||
} else {
|
||||
// c1+(v-c2) -> v+(c1-c2)
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
|
||||
"+",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
// (c1-v)+c2 -> (c1+c2)-v
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.left, "+", expr.right, subExpr.position),
|
||||
"-",
|
||||
subExpr.right, expr.position)
|
||||
} else {
|
||||
// (v-c1)+c2 -> v+(c2-c1)
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.right, "-", subExpr.right, subExpr.position),
|
||||
"+",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="-" && subExpr.operator=="+") {
|
||||
optimizationsDone++
|
||||
if(leftIsConst) {
|
||||
return if(subleftIsConst) {
|
||||
// c1-(c2+v) -> (c1-c2)-v
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "-", subExpr.left, subExpr.position),
|
||||
"-",
|
||||
subExpr.right, expr.position)
|
||||
} else {
|
||||
// c1-(v+c2) -> (c1-c2)-v
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
|
||||
"-",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
// (c1+v)-c2 -> v+(c1-c2)
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.left, "-", expr.right, subExpr.position),
|
||||
"+",
|
||||
subExpr.right, expr.position)
|
||||
} else {
|
||||
// (v+c1)-c2 -> v+(c1-c2)
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.right, "-", expr.right, subExpr.position),
|
||||
"+",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return expr
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(forLoop: ForLoop): Statement {
|
||||
|
||||
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
|
||||
val newFrom: NumericLiteralValue
|
||||
val newTo: NumericLiteralValue
|
||||
@ -537,87 +336,257 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
return RangeExpr(newFrom, newTo, newStep, range.position)
|
||||
}
|
||||
|
||||
val forLoop2 = super.visit(forLoop) as ForLoop
|
||||
|
||||
// check if we need to adjust an array literal to the loop variable's datatype
|
||||
val array = forLoop2.iterable as? ArrayLiteralValue
|
||||
if(array!=null) {
|
||||
val loopvarDt: DataType = when {
|
||||
forLoop.loopVar!=null -> forLoop.loopVar!!.inferType(program).typeOrElse(DataType.UBYTE)
|
||||
forLoop.loopRegister!=null -> DataType.UBYTE
|
||||
else -> throw FatalAstException("weird for loop")
|
||||
}
|
||||
|
||||
val arrayType = when(loopvarDt) {
|
||||
DataType.UBYTE -> DataType.ARRAY_UB
|
||||
DataType.BYTE -> DataType.ARRAY_B
|
||||
DataType.UWORD -> DataType.ARRAY_UW
|
||||
DataType.WORD -> DataType.ARRAY_W
|
||||
DataType.FLOAT -> DataType.ARRAY_F
|
||||
else -> throw FatalAstException("invalid array elt type")
|
||||
}
|
||||
val array2 = array.cast(arrayType)
|
||||
if(array2!=null && array2!==array) {
|
||||
forLoop2.iterable = array2
|
||||
array2.linkParents(forLoop2)
|
||||
}
|
||||
}
|
||||
|
||||
// adjust the datatype of a range expression in for loops to the loop variable.
|
||||
val iterableRange = forLoop2.iterable as? RangeExpr ?: return forLoop2
|
||||
val iterableRange = forLoop.iterable as? RangeExpr ?: return noModifications
|
||||
val rangeFrom = iterableRange.from as? NumericLiteralValue
|
||||
val rangeTo = iterableRange.to as? NumericLiteralValue
|
||||
if(rangeFrom==null || rangeTo==null) return forLoop2
|
||||
if(rangeFrom==null || rangeTo==null) return noModifications
|
||||
|
||||
val loopvar = forLoop2.loopVar?.targetVarDecl(program.namespace)
|
||||
if(loopvar!=null) {
|
||||
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace)!!
|
||||
val stepLiteral = iterableRange.step as? NumericLiteralValue
|
||||
when(loopvar.datatype) {
|
||||
DataType.UBYTE -> {
|
||||
if(rangeFrom.type!= DataType.UBYTE) {
|
||||
// attempt to translate the iterable into ubyte values
|
||||
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(rangeFrom.type!= DataType.BYTE) {
|
||||
// attempt to translate the iterable into byte values
|
||||
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(rangeFrom.type!= DataType.UWORD) {
|
||||
// attempt to translate the iterable into uword values
|
||||
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(rangeFrom.type!= DataType.WORD) {
|
||||
// attempt to translate the iterable into word values
|
||||
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
}
|
||||
}
|
||||
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
|
||||
}
|
||||
}
|
||||
return forLoop2
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
|
||||
// because constant folding can result in arrays that are now suddenly capable
|
||||
// of telling the type of all their elements (for instance, when they contained -2 which
|
||||
// was a prefix expression earlier), we recalculate the array's datatype.
|
||||
val array = super.visit(arrayLiteral)
|
||||
if(array is ArrayLiteralValue) {
|
||||
if(array.type.isKnown)
|
||||
return array
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if(arrayDt.isKnown) {
|
||||
val newArray = arrayLiteral.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||
if(newArray!=null)
|
||||
return newArray
|
||||
private class ShuffleOperands(val expr: BinaryExpression,
|
||||
val exprOperator: String?,
|
||||
val subExpr: BinaryExpression,
|
||||
val newExprLeft: Expression?,
|
||||
val newExprRight: Expression?,
|
||||
val newSubexprLeft: Expression?,
|
||||
val newSubexprRight: Expression?
|
||||
): IAstModification {
|
||||
override fun perform() {
|
||||
if(exprOperator!=null) expr.operator = exprOperator
|
||||
if(newExprLeft!=null) expr.left = newExprLeft
|
||||
if(newExprRight!=null) expr.right = newExprRight
|
||||
if(newSubexprLeft!=null) subExpr.left = newSubexprLeft
|
||||
if(newSubexprRight!=null) subExpr.right = newSubexprRight
|
||||
}
|
||||
}
|
||||
return array
|
||||
|
||||
private fun groupTwoConstsTogether(expr: BinaryExpression,
|
||||
subExpr: BinaryExpression,
|
||||
leftIsConst: Boolean,
|
||||
rightIsConst: Boolean,
|
||||
subleftIsConst: Boolean,
|
||||
subrightIsConst: Boolean): IAstModification?
|
||||
{
|
||||
// todo: this implements only a small set of possible reorderings at this time
|
||||
if(expr.operator==subExpr.operator) {
|
||||
// both operators are the same.
|
||||
// If + or *, we can simply shuffle the const operands around to optimize.
|
||||
if(expr.operator=="+" || expr.operator=="*") {
|
||||
return if(leftIsConst) {
|
||||
if(subleftIsConst)
|
||||
ShuffleOperands(expr, null, subExpr, subExpr.right, null, null, expr.left)
|
||||
else
|
||||
ShuffleOperands(expr, null, subExpr, subExpr.left, null, expr.left, null)
|
||||
} else {
|
||||
if(subleftIsConst)
|
||||
ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
|
||||
else
|
||||
ShuffleOperands(expr, null, subExpr, null, subExpr.left, expr.right, null)
|
||||
}
|
||||
}
|
||||
|
||||
// If - or /, we simetimes must reorder more, and flip operators (- -> +, / -> *)
|
||||
if(expr.operator=="-" || expr.operator=="/") {
|
||||
if(leftIsConst) {
|
||||
return if (subleftIsConst) {
|
||||
ShuffleOperands(expr, if (expr.operator == "-") "+" else "*", subExpr, subExpr.right, null, expr.left, subExpr.left)
|
||||
} else {
|
||||
IAstModification.ReplaceNode(expr,
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
||||
expr.operator, subExpr.left, expr.position),
|
||||
expr.parent)
|
||||
}
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
return ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
|
||||
} else {
|
||||
IAstModification.ReplaceNode(expr,
|
||||
BinaryExpression(
|
||||
subExpr.left, expr.operator,
|
||||
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
||||
expr.position),
|
||||
expr.parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if(expr.operator=="/" && subExpr.operator=="*") {
|
||||
if(leftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// C1/(C2*V) -> (C1/C2)/V
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "/", subExpr.left, subExpr.position),
|
||||
"/",
|
||||
subExpr.right, expr.position)
|
||||
} else {
|
||||
// C1/(V*C2) -> (C1/C2)/V
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
|
||||
"/",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
} else {
|
||||
val change = if(subleftIsConst) {
|
||||
// (C1*V)/C2 -> (C1/C2)*V
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.left, "/", expr.right, subExpr.position),
|
||||
"*",
|
||||
subExpr.right, expr.position)
|
||||
} else {
|
||||
// (V*C1)/C2 -> (C1/C2)*V
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.right, "/", expr.right, subExpr.position),
|
||||
"*",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="*" && subExpr.operator=="/") {
|
||||
if(leftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// C1*(C2/V) -> (C1*C2)/V
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "*", subExpr.left, subExpr.position),
|
||||
"/",
|
||||
subExpr.right, expr.position)
|
||||
} else {
|
||||
// C1*(V/C2) -> (C1/C2)*V
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
|
||||
"*",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
} else {
|
||||
val change = if(subleftIsConst) {
|
||||
// (C1/V)*C2 -> (C1*C2)/V
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.left, "*", expr.right, subExpr.position),
|
||||
"/",
|
||||
subExpr.right, expr.position)
|
||||
} else {
|
||||
// (V/C1)*C2 -> (C1/C2)*V
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.right, "/", subExpr.right, subExpr.position),
|
||||
"*",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="+" && subExpr.operator=="-") {
|
||||
if(leftIsConst){
|
||||
val change = if(subleftIsConst){
|
||||
// c1+(c2-v) -> (c1+c2)-v
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "+", subExpr.left, subExpr.position),
|
||||
"-",
|
||||
subExpr.right, expr.position)
|
||||
} else {
|
||||
// c1+(v-c2) -> v+(c1-c2)
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
|
||||
"+",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
} else {
|
||||
val change = if(subleftIsConst) {
|
||||
// (c1-v)+c2 -> (c1+c2)-v
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.left, "+", expr.right, subExpr.position),
|
||||
"-",
|
||||
subExpr.right, expr.position)
|
||||
} else {
|
||||
// (v-c1)+c2 -> v+(c2-c1)
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.right, "-", subExpr.right, subExpr.position),
|
||||
"+",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="-" && subExpr.operator=="+") {
|
||||
if(leftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// c1-(c2+v) -> (c1-c2)-v
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "-", subExpr.left, subExpr.position),
|
||||
"-",
|
||||
subExpr.right, expr.position)
|
||||
} else {
|
||||
// c1-(v+c2) -> (c1-c2)-v
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
|
||||
"-",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
} else {
|
||||
val change = if(subleftIsConst) {
|
||||
// (c1+v)-c2 -> v+(c1-c2)
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.left, "-", expr.right, subExpr.position),
|
||||
"+",
|
||||
subExpr.right, expr.position)
|
||||
} else {
|
||||
// (v+c1)-c2 -> v+(c1-c2)
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.right, "-", expr.right, subExpr.position),
|
||||
"+",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,11 +22,12 @@ import kotlin.math.pow
|
||||
internal class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
||||
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
if (assignment.aug_op != null)
|
||||
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
@ -82,10 +83,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (newExpr != null)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
else -> return emptyList()
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
@ -297,7 +298,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if(newExpr != null)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
|
||||
@ -609,8 +610,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (amount == 0) {
|
||||
return expr.left
|
||||
}
|
||||
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
when (targetDt) {
|
||||
when (expr.left.inferType(program).typeOrElse(DataType.STRUCT)) {
|
||||
DataType.UBYTE -> {
|
||||
if (amount >= 8) {
|
||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||
|
@ -5,14 +5,23 @@ import prog8.ast.base.ErrorReporter
|
||||
|
||||
|
||||
internal fun Program.constantFold(errors: ErrorReporter) {
|
||||
val replacer = ConstantIdentifierReplacer(this, errors)
|
||||
replacer.visit(this)
|
||||
if(errors.isEmpty()) {
|
||||
replacer.applyModifications()
|
||||
|
||||
val optimizer = ConstantFoldingOptimizer(this, errors)
|
||||
optimizer.visit(this)
|
||||
|
||||
while(errors.isEmpty() && optimizer.optimizationsDone>0) {
|
||||
optimizer.optimizationsDone = 0
|
||||
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
|
||||
optimizer.visit(this)
|
||||
}
|
||||
|
||||
if(errors.isEmpty()) {
|
||||
replacer.visit(this)
|
||||
replacer.applyModifications()
|
||||
}
|
||||
}
|
||||
|
||||
if(errors.isEmpty())
|
||||
modules.forEach { it.linkParents(namespace) } // re-link in final configuration
|
||||
}
|
||||
@ -21,9 +30,11 @@ internal fun Program.constantFold(errors: ErrorReporter) {
|
||||
internal fun Program.optimizeStatements(errors: ErrorReporter): Int {
|
||||
val optimizer = StatementOptimizer(this, errors)
|
||||
optimizer.visit(this)
|
||||
val optimizationCount = optimizer.applyModifications()
|
||||
|
||||
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
|
||||
|
||||
return optimizer.optimizationsDone
|
||||
return optimizationCount
|
||||
}
|
||||
|
||||
internal fun Program.simplifyExpressions() : Int {
|
||||
|
@ -1,46 +0,0 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.NopStatement
|
||||
import prog8.ast.statements.Statement
|
||||
|
||||
internal class FlattenAnonymousScopesAndNopRemover: IAstVisitor {
|
||||
private var scopesToFlatten = mutableListOf<INameScope>()
|
||||
private val nopStatements = mutableListOf<NopStatement>()
|
||||
|
||||
override fun visit(program: Program) {
|
||||
super.visit(program)
|
||||
for(scope in scopesToFlatten.reversed()) {
|
||||
val namescope = scope.parent as INameScope
|
||||
val idx = namescope.statements.indexOf(scope as Statement)
|
||||
if(idx>=0) {
|
||||
val nop = NopStatement.insteadOf(namescope.statements[idx])
|
||||
nop.parent = namescope as Node
|
||||
namescope.statements[idx] = nop
|
||||
namescope.statements.addAll(idx, scope.statements)
|
||||
scope.statements.forEach { it.parent = namescope }
|
||||
visit(nop)
|
||||
}
|
||||
}
|
||||
|
||||
this.nopStatements.forEach {
|
||||
it.definingScope().remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(scope: AnonymousScope) {
|
||||
if(scope.parent is INameScope) {
|
||||
scopesToFlatten.add(scope) // get rid of the anonymous scope
|
||||
}
|
||||
|
||||
return super.visit(scope)
|
||||
}
|
||||
|
||||
override fun visit(nopStatement: NopStatement) {
|
||||
nopStatements.add(nopStatement)
|
||||
}
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
@ -14,54 +16,41 @@ import kotlin.math.floor
|
||||
|
||||
/*
|
||||
TODO: remove unreachable code after return and exit()
|
||||
TODO: proper inlining of tiny subroutines (at first, restrict to subs without parameters and variables in them, and build it up from there: correctly renaming/relocating all variables in them and refs to those as well)
|
||||
*/
|
||||
|
||||
|
||||
// TODO implement using AstWalker instead of IAstModifyingVisitor
|
||||
internal class StatementOptimizer(private val program: Program,
|
||||
private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
var optimizationsDone: Int = 0
|
||||
private set
|
||||
private val errors: ErrorReporter) : AstWalker() {
|
||||
|
||||
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
private val callgraph = CallGraph(program)
|
||||
private val vardeclsToRemove = mutableListOf<VarDecl>()
|
||||
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
|
||||
|
||||
override fun visit(program: Program) {
|
||||
super.visit(program)
|
||||
|
||||
for(decl in vardeclsToRemove) {
|
||||
decl.definingScope().remove(decl)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(block: Block): Statement {
|
||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
if("force_output" !in block.options()) {
|
||||
if (block.containsNoCodeNorVars()) {
|
||||
optimizationsDone++
|
||||
errors.warn("removing empty block '${block.name}'", block.position)
|
||||
return NopStatement.insteadOf(block)
|
||||
return listOf(IAstModification.Remove(block, parent))
|
||||
}
|
||||
|
||||
if (block !in callgraph.usedSymbols) {
|
||||
optimizationsDone++
|
||||
errors.warn("removing unused block '${block.name}'", block.position)
|
||||
return NopStatement.insteadOf(block) // remove unused block
|
||||
return listOf(IAstModification.Remove(block, parent))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
return super.visit(block)
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine): Statement {
|
||||
super.visit(subroutine)
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
val forceOutput = "force_output" in subroutine.definingBlock().options()
|
||||
if(subroutine.asmAddress==null && !forceOutput) {
|
||||
if(subroutine.containsNoCodeNorVars()) {
|
||||
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(subroutine)
|
||||
val removals = callgraph.calledBy.getValue(subroutine).map {
|
||||
IAstModification.Remove(it, it.parent)
|
||||
}.toMutableList()
|
||||
removals += IAstModification.Remove(subroutine, parent)
|
||||
return removals
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,23 +61,359 @@ internal class StatementOptimizer(private val program: Program,
|
||||
|
||||
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
|
||||
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(subroutine)
|
||||
return listOf(IAstModification.Remove(subroutine, parent))
|
||||
}
|
||||
|
||||
return subroutine
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val linesToRemove = deduplicateAssignments(scope.statements)
|
||||
return linesToRemove.reversed().map { IAstModification.Remove(scope.statements[it], scope) }
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val forceOutput = "force_output" in decl.definingBlock().options()
|
||||
if(decl !in callgraph.usedSymbols && !forceOutput) {
|
||||
if(decl.type == VarDeclType.VAR)
|
||||
errors.warn("removing unused variable ${decl.type} '${decl.name}'", decl.position)
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(decl)
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
|
||||
return listOf(IAstModification.Remove(decl, parent))
|
||||
}
|
||||
|
||||
return super.visit(decl)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
|
||||
val functionName = functionCallStatement.target.nameInSource[0]
|
||||
if (functionName in pureBuiltinFunctions) {
|
||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
return listOf(IAstModification.Remove(functionCallStatement, parent))
|
||||
}
|
||||
}
|
||||
|
||||
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
|
||||
// this is a C-64 specific optimization
|
||||
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print")) {
|
||||
val arg = functionCallStatement.args.single()
|
||||
val stringVar: IdentifierReference?
|
||||
stringVar = if(arg is AddressOf) {
|
||||
arg.identifier
|
||||
} else {
|
||||
arg as? IdentifierReference
|
||||
}
|
||||
if(stringVar!=null) {
|
||||
val vardecl = stringVar.targetVarDecl(program.namespace)!!
|
||||
val string = vardecl.value!! as StringLiteralValue
|
||||
val pos = functionCallStatement.position
|
||||
if(string.value.length==1) {
|
||||
val firstCharEncoded = CompilationTarget.encodeString(string.value, string.altEncoding)[0]
|
||||
val chrout = FunctionCallStatement(
|
||||
IdentifierReference(listOf("c64", "CHROUT"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
|
||||
} else if(string.value.length==2) {
|
||||
val firstTwoCharsEncoded = CompilationTarget.encodeString(string.value.take(2), string.altEncoding)
|
||||
val chrout1 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("c64", "CHROUT"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
val chrout2 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("c64", "CHROUT"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
val anonscope = AnonymousScope(mutableListOf(), pos)
|
||||
anonscope.statements.add(chrout1)
|
||||
anonscope.statements.add(chrout2)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, anonscope, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if the first instruction in the called subroutine is a return statement, remove the jump altogeter
|
||||
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is ReturnFromIrq || first is Return)
|
||||
return listOf(IAstModification.Remove(functionCallStatement, parent))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
// if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
|
||||
val subroutine = functionCall.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Return && first.value!=null) {
|
||||
val constval = first.value?.constValue(program)
|
||||
if(constval!=null)
|
||||
return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
||||
// remove empty if statements
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars())
|
||||
return listOf(IAstModification.Remove(ifStatement, parent))
|
||||
|
||||
// empty true part? switch with the else part
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
|
||||
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
|
||||
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
|
||||
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(ifStatement.condition, invertedCondition, ifStatement),
|
||||
IAstModification.ReplaceNode(ifStatement.truepart, truepart, ifStatement),
|
||||
IAstModification.ReplaceNode(ifStatement.elsepart, emptyscope, ifStatement)
|
||||
)
|
||||
}
|
||||
|
||||
val constvalue = ifStatement.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> keep only if-part
|
||||
errors.warn("condition is always true", ifStatement.position)
|
||||
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.truepart, parent))
|
||||
} else {
|
||||
// always false -> keep only else-part
|
||||
errors.warn("condition is always false", ifStatement.position)
|
||||
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.elsepart, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||
if(forLoop.body.containsNoCodeNorVars()) {
|
||||
errors.warn("removing empty for loop", forLoop.position)
|
||||
return listOf(IAstModification.Remove(forLoop, parent))
|
||||
} else if(forLoop.body.statements.size==1) {
|
||||
val loopvar = forLoop.body.statements[0] as? VarDecl
|
||||
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
|
||||
// remove empty for loop (only loopvar decl in it)
|
||||
return listOf(IAstModification.Remove(forLoop, parent))
|
||||
}
|
||||
}
|
||||
|
||||
val range = forLoop.iterable as? RangeExpr
|
||||
if(range!=null) {
|
||||
if(range.size()==1) {
|
||||
// for loop over a (constant) range of just a single value-- optimize the loop away
|
||||
// loopvar/reg = range value , follow by block
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), null, range.from, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
}
|
||||
}
|
||||
val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program.namespace)
|
||||
if(iterable!=null) {
|
||||
if(iterable.datatype==DataType.STR) {
|
||||
val sv = iterable.value as StringLiteralValue
|
||||
val size = sv.value.length
|
||||
if(size==1) {
|
||||
// loop over string of length 1 -> just assign the single character
|
||||
val character = CompilationTarget.encodeString(sv.value, sv.altEncoding)[0]
|
||||
val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position)
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), null, byte, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
}
|
||||
}
|
||||
else if(iterable.datatype in ArrayDatatypes) {
|
||||
val size = iterable.arraysize!!.size()
|
||||
if(size==1) {
|
||||
// loop over array of length 1 -> just assign the single value
|
||||
val av = (iterable.value as ArrayLiteralValue).value[0].constValue(program)?.number
|
||||
if(av!=null) {
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(
|
||||
AssignTarget(forLoop.loopVar, null, null, forLoop.position), null, NumericLiteralValue.optimalInteger(av.toInt(), iterable.position),
|
||||
forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||
val constvalue = untilLoop.untilCondition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
if(constvalue.asBooleanValue) {
|
||||
// always true -> keep only the statement block (if there are no continue and break statements)
|
||||
errors.warn("condition is always true", untilLoop.untilCondition.position)
|
||||
if(!hasContinueOrBreak(untilLoop.body))
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop, untilLoop.body, parent))
|
||||
} else {
|
||||
// always false
|
||||
val forever = RepeatLoop(null, untilLoop.body, untilLoop.position)
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop, forever, parent))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||
val constvalue = whileLoop.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue) {
|
||||
// always true
|
||||
val forever = RepeatLoop(null, whileLoop.body, whileLoop.position)
|
||||
listOf(IAstModification.ReplaceNode(whileLoop, forever, parent))
|
||||
} else {
|
||||
// always false -> remove the while statement altogether
|
||||
errors.warn("condition is always false", whileLoop.condition.position)
|
||||
listOf(IAstModification.Remove(whileLoop, parent))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
|
||||
val iter = repeatLoop.iterations
|
||||
if(iter!=null) {
|
||||
if(repeatLoop.body.containsNoCodeNorVars()) {
|
||||
errors.warn("empty loop removed", repeatLoop.position)
|
||||
return listOf(IAstModification.Remove(repeatLoop, parent))
|
||||
}
|
||||
val iterations = iter.constValue(program)?.number?.toInt()
|
||||
if (iterations == 0) {
|
||||
errors.warn("iterations is always 0, removed loop", iter.position)
|
||||
return listOf(IAstModification.Remove(repeatLoop, parent))
|
||||
}
|
||||
if (iterations == 1)
|
||||
errors.warn("iterations is always 1", iter.position)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
||||
// remove empty choices
|
||||
class ChoiceRemover(val choice: WhenChoice) : IAstModification {
|
||||
override fun perform() {
|
||||
whenStatement.choices.remove(choice)
|
||||
}
|
||||
}
|
||||
return whenStatement.choices
|
||||
.filter { !it.statements.containsCodeOrVars() }
|
||||
.map { ChoiceRemover(it) }
|
||||
}
|
||||
|
||||
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
|
||||
// if the jump is to the next statement, remove the jump
|
||||
val scope = jump.definingScope()
|
||||
val label = jump.identifier?.targetStatement(scope)
|
||||
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
|
||||
return listOf(IAstModification.Remove(jump, parent))
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
if(assignment.aug_op!=null)
|
||||
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
|
||||
|
||||
// remove assignments to self
|
||||
if(assignment.target isSameAs assignment.value) {
|
||||
if(assignment.target.isNotMemory(program.namespace))
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
}
|
||||
|
||||
val targetIDt = assignment.target.inferType(program, assignment)
|
||||
if(!targetIDt.isKnown)
|
||||
throw FatalAstException("can't infer type of assignment target")
|
||||
|
||||
|
||||
// optimize binary expressions a bit
|
||||
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
|
||||
val bexpr=assignment.value as? BinaryExpression
|
||||
if(bexpr!=null) {
|
||||
val cv = bexpr.right.constValue(program)?.number?.toDouble()
|
||||
if (cv != null && assignment.target isSameAs bexpr.left) {
|
||||
// assignments of the form: X = X <operator> <expr>
|
||||
// remove assignments that have no effect (such as X=X+0)
|
||||
// optimize/rewrite some other expressions
|
||||
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
|
||||
when (bexpr.operator) {
|
||||
"+" -> {
|
||||
if (cv == 0.0) {
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
||||
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
|
||||
// replace by several INCs (a bit less when dealing with memory targets)
|
||||
val incs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(cv.toInt()) {
|
||||
incs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
if (cv == 0.0) {
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
||||
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
|
||||
// replace by several DECs (a bit less when dealing with memory targets)
|
||||
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(cv.toInt()) {
|
||||
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(assignment, decs, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
"*" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"/" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"**" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"|" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"^" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"<<" -> {
|
||||
if (cv == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
// replace by in-place lsl(...) call
|
||||
val scope = AnonymousScope(mutableListOf(), assignment.position)
|
||||
var numshifts = cv.toInt()
|
||||
while (numshifts > 0) {
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsl"), assignment.position),
|
||||
mutableListOf(bexpr.left), true, assignment.position))
|
||||
numshifts--
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(assignment, scope, parent))
|
||||
}
|
||||
">>" -> {
|
||||
if (cv == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
// replace by in-place lsr(...) call
|
||||
val scope = AnonymousScope(mutableListOf(), assignment.position)
|
||||
var numshifts = cv.toInt()
|
||||
while (numshifts > 0) {
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsr"), assignment.position),
|
||||
mutableListOf(bexpr.left), true, assignment.position))
|
||||
numshifts--
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(assignment, scope, parent))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun deduplicateAssignments(statements: List<Statement>): MutableList<Int> {
|
||||
@ -116,207 +441,6 @@ internal class StatementOptimizer(private val program: Program,
|
||||
return linesToRemove
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
|
||||
val functionName = functionCallStatement.target.nameInSource[0]
|
||||
if (functionName in pureBuiltinFunctions) {
|
||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(functionCallStatement)
|
||||
}
|
||||
}
|
||||
|
||||
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") ||
|
||||
functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) {
|
||||
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
|
||||
val arg = functionCallStatement.args.single()
|
||||
val stringVar: IdentifierReference?
|
||||
stringVar = if(arg is AddressOf) {
|
||||
arg.identifier
|
||||
} else {
|
||||
arg as? IdentifierReference
|
||||
}
|
||||
if(stringVar!=null) {
|
||||
val vardecl = stringVar.targetVarDecl(program.namespace)!!
|
||||
val string = vardecl.value!! as StringLiteralValue
|
||||
if(string.value.length==1) {
|
||||
val firstCharEncoded = CompilationTarget.encodeString(string.value, string.altEncoding)[0]
|
||||
functionCallStatement.args.clear()
|
||||
functionCallStatement.args.add(NumericLiteralValue.optimalInteger(firstCharEncoded.toInt(), functionCallStatement.position))
|
||||
functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position)
|
||||
vardeclsToRemove.add(vardecl)
|
||||
optimizationsDone++
|
||||
return functionCallStatement
|
||||
} else if(string.value.length==2) {
|
||||
val firstTwoCharsEncoded = CompilationTarget.encodeString(string.value.take(2), string.altEncoding)
|
||||
val scope = AnonymousScope(mutableListOf(), functionCallStatement.position)
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
|
||||
mutableListOf(NumericLiteralValue.optimalInteger(firstTwoCharsEncoded[0].toInt(), functionCallStatement.position)),
|
||||
functionCallStatement.void, functionCallStatement.position))
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
|
||||
mutableListOf(NumericLiteralValue.optimalInteger(firstTwoCharsEncoded[1].toInt(), functionCallStatement.position)),
|
||||
functionCallStatement.void, functionCallStatement.position))
|
||||
vardeclsToRemove.add(vardecl)
|
||||
optimizationsDone++
|
||||
return scope
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if it calls a subroutine,
|
||||
// and the first instruction in the subroutine is a jump, call that jump target instead
|
||||
// if the first instruction in the subroutine is a return statement, replace with a nop instruction
|
||||
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Jump && first.identifier!=null) {
|
||||
optimizationsDone++
|
||||
return FunctionCallStatement(first.identifier, functionCallStatement.args, functionCallStatement.void, functionCallStatement.position)
|
||||
}
|
||||
if(first is ReturnFromIrq || first is Return) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(functionCallStatement)
|
||||
}
|
||||
}
|
||||
|
||||
return super.visit(functionCallStatement)
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall): Expression {
|
||||
// if it calls a subroutine,
|
||||
// and the first instruction in the subroutine is a jump, call that jump target instead
|
||||
// if the first instruction in the subroutine is a return statement with constant value, replace with the constant value
|
||||
val subroutine = functionCall.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Jump && first.identifier!=null) {
|
||||
optimizationsDone++
|
||||
return FunctionCall(first.identifier, functionCall.args, functionCall.position)
|
||||
}
|
||||
if(first is Return && first.value!=null) {
|
||||
val constval = first.value?.constValue(program)
|
||||
if(constval!=null)
|
||||
return constval
|
||||
}
|
||||
}
|
||||
return super.visit(functionCall)
|
||||
}
|
||||
|
||||
override fun visit(ifStatement: IfStatement): Statement {
|
||||
super.visit(ifStatement)
|
||||
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars()) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(ifStatement)
|
||||
}
|
||||
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
|
||||
// invert the condition and move else part to true part
|
||||
ifStatement.truepart = ifStatement.elsepart
|
||||
ifStatement.elsepart = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
|
||||
ifStatement.condition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
|
||||
optimizationsDone++
|
||||
return ifStatement
|
||||
}
|
||||
|
||||
val constvalue = ifStatement.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> keep only if-part
|
||||
errors.warn("condition is always true", ifStatement.position)
|
||||
optimizationsDone++
|
||||
ifStatement.truepart
|
||||
} else {
|
||||
// always false -> keep only else-part
|
||||
errors.warn("condition is always false", ifStatement.position)
|
||||
optimizationsDone++
|
||||
ifStatement.elsepart
|
||||
}
|
||||
}
|
||||
return ifStatement
|
||||
}
|
||||
|
||||
override fun visit(forLoop: ForLoop): Statement {
|
||||
super.visit(forLoop)
|
||||
if(forLoop.body.containsNoCodeNorVars()) {
|
||||
// remove empty for loop
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(forLoop)
|
||||
} else if(forLoop.body.statements.size==1) {
|
||||
val loopvar = forLoop.body.statements[0] as? VarDecl
|
||||
if(loopvar!=null && loopvar.name==forLoop.loopVar?.nameInSource?.singleOrNull()) {
|
||||
// remove empty for loop
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(forLoop)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val range = forLoop.iterable as? RangeExpr
|
||||
if(range!=null) {
|
||||
if(range.size()==1) {
|
||||
// for loop over a (constant) range of just a single value-- optimize the loop away
|
||||
// loopvar/reg = range value , follow by block
|
||||
val assignment = Assignment(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, range.from, forLoop.position)
|
||||
forLoop.body.statements.add(0, assignment)
|
||||
optimizationsDone++
|
||||
return forLoop.body
|
||||
}
|
||||
}
|
||||
return forLoop
|
||||
}
|
||||
|
||||
override fun visit(whileLoop: WhileLoop): Statement {
|
||||
super.visit(whileLoop)
|
||||
val constvalue = whileLoop.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> print a warning, and optimize into a forever-loop
|
||||
errors.warn("condition is always true", whileLoop.condition.position)
|
||||
optimizationsDone++
|
||||
ForeverLoop(whileLoop.body, whileLoop.position)
|
||||
} else {
|
||||
// always false -> remove the while statement altogether
|
||||
errors.warn("condition is always false", whileLoop.condition.position)
|
||||
optimizationsDone++
|
||||
NopStatement.insteadOf(whileLoop)
|
||||
}
|
||||
}
|
||||
return whileLoop
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop): Statement {
|
||||
super.visit(repeatLoop)
|
||||
val constvalue = repeatLoop.untilCondition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> keep only the statement block (if there are no continue and break statements)
|
||||
errors.warn("condition is always true", repeatLoop.untilCondition.position)
|
||||
if(hasContinueOrBreak(repeatLoop.body))
|
||||
repeatLoop
|
||||
else {
|
||||
optimizationsDone++
|
||||
repeatLoop.body
|
||||
}
|
||||
} else {
|
||||
// always false -> print a warning, and optimize into a forever loop
|
||||
errors.warn("condition is always false", repeatLoop.untilCondition.position)
|
||||
optimizationsDone++
|
||||
ForeverLoop(repeatLoop.body, repeatLoop.position)
|
||||
}
|
||||
}
|
||||
return repeatLoop
|
||||
}
|
||||
|
||||
override fun visit(whenStatement: WhenStatement): Statement {
|
||||
val choices = whenStatement.choices.toList()
|
||||
for(choice in choices) {
|
||||
if(choice.statements.containsNoCodeNorVars())
|
||||
whenStatement.choices.remove(choice)
|
||||
}
|
||||
return super.visit(whenStatement)
|
||||
}
|
||||
|
||||
private fun hasContinueOrBreak(scope: INameScope): Boolean {
|
||||
|
||||
class Searcher: IAstVisitor
|
||||
@ -341,185 +465,4 @@ internal class StatementOptimizer(private val program: Program,
|
||||
return s.count > 0
|
||||
}
|
||||
|
||||
override fun visit(jump: Jump): Statement {
|
||||
val subroutine = jump.identifier?.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
// if the first instruction in the subroutine is another jump, shortcut this one
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Jump) {
|
||||
optimizationsDone++
|
||||
return first
|
||||
}
|
||||
}
|
||||
|
||||
// if the jump is to the next statement, remove the jump
|
||||
val scope = jump.definingScope()
|
||||
val label = jump.identifier?.targetStatement(scope)
|
||||
if(label!=null) {
|
||||
if(scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(jump)
|
||||
}
|
||||
}
|
||||
|
||||
return jump
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment): Statement {
|
||||
if(assignment.aug_op!=null)
|
||||
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)) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
}
|
||||
val targetIDt = assignment.target.inferType(program, assignment)
|
||||
if(!targetIDt.isKnown)
|
||||
throw FatalAstException("can't infer type of assignment target")
|
||||
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
|
||||
val bexpr=assignment.value as? BinaryExpression
|
||||
if(bexpr!=null) {
|
||||
val cv = bexpr.right.constValue(program)?.number?.toDouble()
|
||||
if (cv == null) {
|
||||
if (bexpr.operator == "+" && targetDt != DataType.FLOAT) {
|
||||
if (bexpr.left isSameAs bexpr.right && assignment.target isSameAs bexpr.left) {
|
||||
bexpr.operator = "*"
|
||||
bexpr.right = NumericLiteralValue.optimalInteger(2, assignment.value.position)
|
||||
optimizationsDone++
|
||||
return assignment
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (assignment.target isSameAs bexpr.left) {
|
||||
// remove assignments that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc
|
||||
// A = A <operator> B
|
||||
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
|
||||
|
||||
when (bexpr.operator) {
|
||||
"+" -> {
|
||||
if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
||||
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
|
||||
// replace by several INCs (a bit less when dealing with memory targets)
|
||||
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(cv.toInt()) {
|
||||
decs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
|
||||
}
|
||||
return decs
|
||||
}
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
||||
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
|
||||
// replace by several DECs (a bit less when dealing with memory targets)
|
||||
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(cv.toInt()) {
|
||||
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
|
||||
}
|
||||
return decs
|
||||
}
|
||||
}
|
||||
}
|
||||
"*" -> if (cv == 1.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
"/" -> if (cv == 1.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
"**" -> if (cv == 1.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
"|" -> if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
"^" -> if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
"<<" -> {
|
||||
if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) ||
|
||||
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
|
||||
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
|
||||
assignment.value.linkParents(assignment)
|
||||
optimizationsDone++
|
||||
} else {
|
||||
// replace by in-place lsl(...) call
|
||||
val scope = AnonymousScope(mutableListOf(), assignment.position)
|
||||
var numshifts = cv.toInt()
|
||||
while (numshifts > 0) {
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsl"), assignment.position),
|
||||
mutableListOf(bexpr.left), true, assignment.position))
|
||||
numshifts--
|
||||
}
|
||||
optimizationsDone++
|
||||
return scope
|
||||
}
|
||||
}
|
||||
">>" -> {
|
||||
if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
if ((targetDt == DataType.UWORD && cv > 15.0) || (targetDt == DataType.UBYTE && cv > 7.0)) {
|
||||
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
|
||||
assignment.value.linkParents(assignment)
|
||||
optimizationsDone++
|
||||
} else {
|
||||
// replace by in-place lsr(...) call
|
||||
val scope = AnonymousScope(mutableListOf(), assignment.position)
|
||||
var numshifts = cv.toInt()
|
||||
while (numshifts > 0) {
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsr"), assignment.position),
|
||||
mutableListOf(bexpr.left), true, assignment.position))
|
||||
numshifts--
|
||||
}
|
||||
optimizationsDone++
|
||||
return scope
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return super.visit(assignment)
|
||||
}
|
||||
|
||||
override fun visit(scope: AnonymousScope): Statement {
|
||||
val linesToRemove = deduplicateAssignments(scope.statements)
|
||||
if(linesToRemove.isNotEmpty()) {
|
||||
linesToRemove.reversed().forEach{scope.statements.removeAt(it)}
|
||||
}
|
||||
return super.visit(scope)
|
||||
}
|
||||
|
||||
override fun visit(label: Label): Statement {
|
||||
// remove duplicate labels
|
||||
val stmts = label.definingScope().statements
|
||||
val startIdx = stmts.indexOf(label)
|
||||
if(startIdx< stmts.lastIndex && stmts[startIdx+1] == label)
|
||||
return NopStatement.insteadOf(label)
|
||||
|
||||
return super.visit(label)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -17,7 +17,7 @@ internal class UnusedCodeRemover: AstWalker() {
|
||||
val entrypoint = program.entrypoint()
|
||||
program.modules.forEach {
|
||||
callgraph.forAllSubroutines(it) { sub ->
|
||||
if (sub !== entrypoint && !sub.keepAlways && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
|
||||
if (sub !== entrypoint && !sub.keepAlways && (callgraph.calledBy[sub].isNullOrEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
|
||||
removals.add(IAstModification.Remove(sub, sub.definingScope() as Node))
|
||||
}
|
||||
}
|
||||
@ -30,7 +30,7 @@ internal class UnusedCodeRemover: AstWalker() {
|
||||
// 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?
|
||||
removals.add(IAstModification.Remove(it, it.parent))
|
||||
}
|
||||
|
||||
return removals
|
||||
|
@ -139,8 +139,8 @@ Design principles and features
|
||||
- 'One statement per line' code, resulting in clear readable programs.
|
||||
- Modular programming and scoping via modules, code blocks, and subroutines.
|
||||
- Provide high level programming constructs but at the same time stay close to the metal;
|
||||
still able to directly use memory addresses, CPU registers and ROM subroutines,
|
||||
and inline assembly to have full control when every cycle or byte matters
|
||||
still able to directly use memory addresses and ROM subroutines,
|
||||
and inline assembly to have full control when every register, cycle or byte matters
|
||||
- Arbitrary number of subroutine parameters
|
||||
- Complex nested expressions are possible
|
||||
- Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters
|
||||
@ -156,7 +156,7 @@ Design principles and features
|
||||
- The compiler tries to optimize the program and generated code a bit, but hand-tuning of the
|
||||
performance or space-critical parts will likely still be required. This is supported by
|
||||
the ability to easily write embedded assembly code directly in the program source code.
|
||||
- There are many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
|
||||
- There are many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``substr``, ``sort`` and ``reverse`` (and others)
|
||||
- Assembling the generated code into a program wil be done by an external cross-assembler tool.
|
||||
|
||||
|
||||
|
@ -50,7 +50,7 @@ Code
|
||||
There are different kinds of instructions ('statements' is a better name) such as:
|
||||
|
||||
- value assignment
|
||||
- looping (for, while, repeat, unconditional jumps)
|
||||
- looping (for, while, do-until, repeat, unconditional jumps)
|
||||
- conditional execution (if - then - else, when, and conditional jumps)
|
||||
- subroutine calls
|
||||
- label definition
|
||||
@ -137,7 +137,7 @@ Scopes are created using either of these two statements:
|
||||
|
||||
.. important::
|
||||
Unlike most other programming languages, a new scope is *not* created inside
|
||||
for, while and repeat statements, the if statement, and the branching conditionals.
|
||||
for, while, repeat, and do-until statements, the if statement, and the branching conditionals.
|
||||
These all share the same scope from the subroutine they're defined in.
|
||||
You can define variables in these blocks, but these will be treated as if they
|
||||
were defined in the subroutine instead.
|
||||
@ -204,13 +204,6 @@ Example::
|
||||
byte @zp zeropageCounter = 42
|
||||
|
||||
|
||||
Variables that represent CPU hardware registers
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The following variables are reserved
|
||||
and map directly (read/write) to a CPU hardware register: ``A``, ``X``, ``Y``.
|
||||
|
||||
|
||||
Integers
|
||||
^^^^^^^^
|
||||
|
||||
@ -279,16 +272,23 @@ This @-prefix can also be used for character byte values.
|
||||
|
||||
|
||||
You can concatenate two string literals using '+' (not very useful though) or repeat
|
||||
a string literal a given number of times using '*'::
|
||||
a string literal a given number of times using '*'. You can also assign a new string
|
||||
value to another string. No bounds check is done so be sure the destination string is
|
||||
large enough to contain the new value::
|
||||
|
||||
str string1 = "first part" + "second part"
|
||||
str string2 = "hello!" * 10
|
||||
|
||||
string1 = string2
|
||||
string1 = "new value"
|
||||
|
||||
|
||||
.. caution::
|
||||
Avoid changing strings after they've been created.
|
||||
It's probably best to avoid changing strings after they've been created. This
|
||||
includes changing certain letters by index, or by assigning a new value, or by
|
||||
modifying the string via other means for example ``substr`` function and its cousins.
|
||||
This is because if your program exits and is restarted (without loading it again),
|
||||
it will then start working with the changed strings instead of the original ones.
|
||||
it will then start working with the changed strings instead of the original ones!
|
||||
The same is true for arrays.
|
||||
|
||||
|
||||
@ -386,21 +386,19 @@ expected when the program is restarted.
|
||||
Loops
|
||||
-----
|
||||
|
||||
The *for*-loop is used to let a variable (or register) iterate over a range of values. Iteration is done in steps of 1, but you can change this.
|
||||
The *for*-loop is used to let a variable iterate over a range of values. Iteration is done in steps of 1, but you can change this.
|
||||
The loop variable must be declared as byte or word earlier so you can reuse it for multiple occasions.
|
||||
Iterating with a floating point variable is not supported. If you want to loop over a floating-point array, use a loop with an integer index variable instead.
|
||||
|
||||
The *while*-loop is used to repeat a piece of code while a certain condition is still true.
|
||||
The *repeat--until* loop is used to repeat a piece of code until a certain condition is true.
|
||||
|
||||
The *forever*-loop is used to simply run a piece of code in a loop, forever. You can still
|
||||
break out of this loop if desired. A "while true" or "until false" loop is equivalent to
|
||||
a forever-loop.
|
||||
The *do--until* loop is used to repeat a piece of code until a certain condition is true.
|
||||
The *repeat* loop is used as a short notation of a for loop where the loop variable doesn't matter and you're only interested in the number of iterations.
|
||||
(without iteration count specified it simply loops forever).
|
||||
|
||||
You can also create loops by using the ``goto`` statement, but this should usually be avoided.
|
||||
|
||||
.. attention::
|
||||
The value of the loop variable or register after executing the loop *is undefined*. Don't use it immediately
|
||||
The value of the loop variable after executing the loop *is undefined*. Don't use it immediately
|
||||
after the loop without first assigning a new value to it!
|
||||
(this is an optimization issue to avoid having to deal with mostly useless post-loop logic to adjust the loop variable's value)
|
||||
|
||||
@ -414,15 +412,15 @@ if statements
|
||||
Conditional execution means that the flow of execution changes based on certiain conditions,
|
||||
rather than having fixed gotos or subroutine calls::
|
||||
|
||||
if A>4 goto overflow
|
||||
if aa>4 goto overflow
|
||||
|
||||
if X==3 Y = 4
|
||||
if X==3 Y = 4 else A = 2
|
||||
if xx==3 yy = 4
|
||||
if xx==3 yy = 4 else aa = 2
|
||||
|
||||
if X==5 {
|
||||
Y = 99
|
||||
if xx==5 {
|
||||
yy = 99
|
||||
} else {
|
||||
A = 3
|
||||
aa = 3
|
||||
}
|
||||
|
||||
|
||||
@ -486,16 +484,16 @@ Assignments
|
||||
-----------
|
||||
|
||||
Assignment statements assign a single value to a target variable or memory location.
|
||||
Augmented assignments (such as ``A += X``) are also available, but these are just shorthands
|
||||
for normal assignments (``A = A + X``).
|
||||
Augmented assignments (such as ``aa += xx``) are also available, but these are just shorthands
|
||||
for normal assignments (``aa = aa + xx``).
|
||||
|
||||
Only register variables and variables of type byte, word and float can be assigned a new value.
|
||||
Only variables of type byte, word and float can be assigned a new value.
|
||||
It's not possible to set a new value to string or array variables etc, because they get allocated
|
||||
a fixed amount of memory which will not change.
|
||||
a fixed amount of memory which will not change. (You *can* change the value of elements in a string or array though).
|
||||
|
||||
.. attention::
|
||||
**Data type conversion (in assignments):**
|
||||
When assigning a value with a 'smaller' datatype to a register or variable with a 'larger' datatype,
|
||||
When assigning a value with a 'smaller' datatype to variable with a 'larger' datatype,
|
||||
the value will be automatically converted to the target datatype: byte --> word --> float.
|
||||
So assigning a byte to a word variable, or a word to a floating point variable, is fine.
|
||||
The reverse is *not* true: it is *not* possible to assign a value of a 'larger' datatype to
|
||||
@ -511,7 +509,7 @@ as the memory mapped address $d021.
|
||||
If you want to access a memory location directly (by using the address itself), without defining
|
||||
a memory mapped location, you can do so by enclosing the address in ``@(...)``::
|
||||
|
||||
A = @($d020) ; set the A register to the current c64 screen border color ("peek(53280)")
|
||||
color = @($d020) ; set the variable 'color' to the current c64 screen border color ("peek(53280)")
|
||||
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
|
||||
@(vic+$20) = 6 ; you can also use expressions to 'calculate' the address
|
||||
|
||||
@ -802,6 +800,22 @@ memsetw(address, numwords, wordvalue)
|
||||
Efficiently set a part of memory to the given (u)word value.
|
||||
But the most efficient will always be to write a specialized fill routine in assembly yourself!
|
||||
|
||||
leftstr(source, target, length)
|
||||
Copies the left side of the source string of the given length to target string.
|
||||
It is assumed the target string buffer is large enough to contain the result.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
rightstr(source, target, length)
|
||||
Copies the right side of the source string of the given length to target string.
|
||||
It is assumed the target string buffer is large enough to contain the result.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
substr(source, target, start, length)
|
||||
Copies a segment from the source string, starting at the given index,
|
||||
and of the given length to target string.
|
||||
It is assumed the target string buffer is large enough to contain the result.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
swap(x, y)
|
||||
Swap the values of numerical variables (or memory locations) x and y in a fast way.
|
||||
|
||||
|
@ -24,7 +24,7 @@ Everything after a semicolon ``;`` is a comment and is ignored.
|
||||
If the whole line is just a comment, it will be copied into the resulting assembly source code.
|
||||
This makes it easier to understand and relate the generated code. Examples::
|
||||
|
||||
A = 42 ; set the initial value to 42
|
||||
counter = 42 ; set the initial value to 42
|
||||
; next is the code that...
|
||||
|
||||
|
||||
@ -313,7 +313,7 @@ Direct access to memory locations
|
||||
Instead of defining a memory mapped name for a specific memory location, you can also
|
||||
directly access the memory. Enclose a numeric expression or literal with ``@(...)`` to do that::
|
||||
|
||||
A = @($d020) ; set the A register to the current c64 screen border color ("peek(53280)")
|
||||
color = @($d020) ; set the variable 'color' to the current c64 screen border color ("peek(53280)")
|
||||
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
|
||||
@(vic+$20) = 6 ; a dynamic expression to 'calculate' the address
|
||||
|
||||
@ -333,8 +333,6 @@ Reserved names
|
||||
|
||||
The following names are reserved, they have a special meaning::
|
||||
|
||||
A X Y ; 6502 hardware registers
|
||||
Pc Pz Pn Pv ; 6502 status register flags
|
||||
true false ; boolean values 1 and 0
|
||||
|
||||
|
||||
@ -406,10 +404,10 @@ assignment: ``=``
|
||||
Note that an assignment sometimes is not possible or supported.
|
||||
|
||||
augmented assignment: ``+=`` ``-=`` ``*=`` ``/=`` ``**=`` ``&=`` ``|=`` ``^=`` ``<<=`` ``>>=``
|
||||
Syntactic sugar; ``A += X`` is equivalent to ``A = A + X``
|
||||
Syntactic sugar; ``aa += xx`` is equivalent to ``aa = aa + xx``
|
||||
|
||||
postfix increment and decrement: ``++`` ``--``
|
||||
Syntactic sugar; ``A++`` is equivalent to ``A = A + 1``, and ``A--`` is equivalent to ``A = A - 1``.
|
||||
Syntactic sugar; ``aa++`` is equivalent to ``aa = aa + 1``, and ``aa--`` is equivalent to ``aa = aa - 1``.
|
||||
Because these operations are so common, we have these short forms.
|
||||
|
||||
comparison: ``!=`` ``<`` ``>`` ``<=`` ``>=``
|
||||
@ -427,9 +425,9 @@ range creation: ``to``
|
||||
|
||||
0 to 7 ; range of values 0, 1, 2, 3, 4, 5, 6, 7 (constant)
|
||||
|
||||
A = 5
|
||||
X = 10
|
||||
A to X ; range of 5, 6, 7, 8, 9, 10
|
||||
aa = 5
|
||||
aa = 10
|
||||
aa to xx ; range of 5, 6, 7, 8, 9, 10
|
||||
|
||||
byte[] array = 10 to 13 ; sets the array to [1, 2, 3, 4]
|
||||
|
||||
@ -551,7 +549,7 @@ Loops
|
||||
for loop
|
||||
^^^^^^^^
|
||||
|
||||
The loop variable must be a register or a byte/word variable,
|
||||
The loop variable must be a byte or word variable,
|
||||
and must be defined first in the local scope of the for loop.
|
||||
The expression that you loop over can be anything that supports iteration (such as ranges like ``0 to 100``,
|
||||
array variables and strings) *except* floating-point arrays (because a floating-point loop variable is not supported).
|
||||
@ -598,31 +596,33 @@ You can use a single statement, or a statement block like in the example below::
|
||||
}
|
||||
|
||||
|
||||
repeat-until loop
|
||||
^^^^^^^^^^^^^^^^^
|
||||
do-until loop
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Until the given condition is true (1), repeat the given statement(s).
|
||||
You can use a single statement, or a statement block like in the example below::
|
||||
|
||||
repeat {
|
||||
do {
|
||||
; do something...
|
||||
break ; break out of the loop
|
||||
continue ; immediately enter next iteration
|
||||
} until <condition>
|
||||
|
||||
|
||||
forever loop
|
||||
^^^^^^^^^^^^
|
||||
repeat loop
|
||||
^^^^^^^^^^^
|
||||
|
||||
Simply run the code in a loop, forever. It's the same as a while true or until false loop,
|
||||
or just a jump back to a previous label. You can still break out of this loop as well, if you want::
|
||||
When you're only interested in repeating something a given number of times.
|
||||
It's a short hand for a for loop without an explicit loop variable::
|
||||
|
||||
forever {
|
||||
; .. do stuff
|
||||
if something
|
||||
break ; you can exit the loop if you want
|
||||
repeat 15 {
|
||||
; do something...
|
||||
break ; you can break out of the loop
|
||||
}
|
||||
|
||||
If you omit the iteration count, it simply loops forever.
|
||||
You can still ``break`` out of such a loop if you want though.
|
||||
|
||||
|
||||
Conditional Execution and Jumps
|
||||
-------------------------------
|
||||
@ -702,3 +702,4 @@ case you have to use { } to enclose them::
|
||||
}
|
||||
else -> c64scr.print("don't know")
|
||||
}
|
||||
|
||||
|
@ -113,23 +113,15 @@ CPU
|
||||
Directly Usable Registers
|
||||
-------------------------
|
||||
|
||||
The following 6502 CPU hardware registers are directly usable in program code (and are reserved symbols):
|
||||
The hardware CPU registers are not directly accessible from regular Prog8 code.
|
||||
If you need to mess with them, you'll have to use inline assembly.
|
||||
Be extra wary of the ``X`` register because it is used as an evaluation stack pointer and
|
||||
changing its value you will destroy the evaluation stack and likely crash the program.
|
||||
|
||||
- ``A``, ``X``, ``Y`` the three main cpu registers (8 bits)
|
||||
- the status register (P) carry flag and interrupt disable flag can be written via a couple of special
|
||||
The status register (P) carry flag and interrupt disable flag can be written via a couple of special
|
||||
builtin functions (``set_carry()``, ``clear_carry()``, ``set_irqd()``, ``clear_irqd()``),
|
||||
and read via the ``read_flags()`` function.
|
||||
|
||||
However, you must assume that the 3 hardware registers ``A``, ``X`` and ``Y``
|
||||
are volatile. Their values cannot be depended upon, the compiler will use them as required.
|
||||
Even simple assignments may require modification of one or more of the registers (for instance, when using arrays).
|
||||
|
||||
Even more important, the ``X`` register is used as an evaluation stack pointer.
|
||||
If you mess with it, you will destroy the evaluation stack and likely crash your program.
|
||||
In some cases the compiler will warn you about this, but you should really avoid to use
|
||||
this register. It's possible to store/restore the register's value (using special built in functions)
|
||||
for the cases you really really need to use it directly.
|
||||
|
||||
|
||||
Subroutine Calling Conventions
|
||||
------------------------------
|
||||
@ -173,3 +165,4 @@ as a subroutine ``irq`` in the module ``irq`` so like this::
|
||||
; ... irq handling here ...
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,10 +3,10 @@ TODO
|
||||
====
|
||||
|
||||
- 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)
|
||||
- investigate support for 8bitguy's Commander X16 platform https://murray2.com/forums/commander-x16.9/ and https://github.com/commanderx16/x16-docs
|
||||
- investigate support for 8bitguy's Commander X16 platform https://www.commanderx16.com and https://github.com/commanderx16/x16-docs
|
||||
- see if we can group some errors together for instance the (now single) errors about unidentified symbols
|
||||
|
||||
|
||||
More optimizations
|
||||
@ -17,13 +17,13 @@ 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)
|
||||
the program will then rely solely on the values as they are in memory at the time of program startup.
|
||||
- Also some library routines and code patterns could perhaps be optimized further
|
||||
- can the parameter passing to subroutines be optimized to avoid copying?
|
||||
- more optimizations on the language AST level
|
||||
- more optimizations on the final assembly source level
|
||||
- note: abandoned subroutine inlining because of problems referencing non-local stuff. Can't move everything around.
|
||||
|
||||
|
||||
Eval stack redesign? (lot of work)
|
||||
|
@ -104,17 +104,6 @@ main {
|
||||
ub = all(farr)
|
||||
if ub==0 c64scr.print("error all10\n")
|
||||
|
||||
check_eval_stack()
|
||||
|
||||
c64scr.print("\nyou should see no errors printed above (only at first run).")
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
%import c64utils
|
||||
;%import c64flt
|
||||
;%option enable_floats
|
||||
%zeropage dontuse
|
||||
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
ubyte A
|
||||
|
||||
c64scr.print("ubyte shift left\n")
|
||||
A = shiftlb0()
|
||||
c64scr.print_ubbin(A, true)
|
||||
|
@ -24,8 +24,6 @@ main {
|
||||
|
||||
div_float(0,1,0)
|
||||
div_float(999.9,111.0,9.008108108108107)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub div_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||
@ -103,12 +101,4 @@ main {
|
||||
c64flt.print_f(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,8 +32,6 @@ main {
|
||||
minus_float(0,0,0)
|
||||
minus_float(2.5,1.5,1.0)
|
||||
minus_float(-1.5,3.5,-5.0)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub minus_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||
@ -111,13 +109,4 @@ main {
|
||||
c64flt.print_f(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,8 +26,6 @@ main {
|
||||
mul_float(0,0,0)
|
||||
mul_float(2.5,10,25)
|
||||
mul_float(-1.5,10,-15)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub mul_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||
@ -105,12 +103,4 @@ main {
|
||||
c64flt.print_f(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,8 +30,6 @@ main {
|
||||
plus_float(1.5,2.5,4.0)
|
||||
plus_float(-1.5,3.5,2.0)
|
||||
plus_float(-1.1,3.3,2.2)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub plus_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||
@ -109,13 +107,4 @@ main {
|
||||
c64flt.print_f(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ main {
|
||||
|
||||
c64scr.plot(0,24)
|
||||
|
||||
ubyte Y
|
||||
ubyte ub=200
|
||||
byte bb=-100
|
||||
uword uw = 2000
|
||||
@ -76,8 +77,6 @@ main {
|
||||
check_b(barr[1], -100)
|
||||
check_uw(uwarr[1], 2000)
|
||||
check_w(warr[1], -1000)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub check_ub(ubyte value, ubyte expected) {
|
||||
@ -139,13 +138,4 @@ main {
|
||||
c64flt.print_f(expected)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,6 @@ main {
|
||||
remainder_uword(40000,511,142)
|
||||
remainder_uword(40000,500,0)
|
||||
remainder_uword(43211,12,11)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub remainder_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||
@ -48,12 +46,4 @@ main {
|
||||
c64scr.print_uw(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ main {
|
||||
ubyte active_height = 24
|
||||
ubyte upwards = true
|
||||
|
||||
forever {
|
||||
repeat {
|
||||
ubyte mountain = 223 ; slope upwards
|
||||
if active_height < target_height {
|
||||
active_height++
|
||||
|
@ -16,7 +16,7 @@ sub start() {
|
||||
void c64.CHRIN()
|
||||
c64.CLEARSCR()
|
||||
|
||||
forever {
|
||||
repeat {
|
||||
uword note
|
||||
for note in notes {
|
||||
ubyte note1 = lsb(note)
|
||||
@ -37,10 +37,8 @@ sub start() {
|
||||
}
|
||||
|
||||
sub delay() {
|
||||
ubyte d
|
||||
for d in 0 to 12 {
|
||||
while c64.RASTER!=0 {
|
||||
; tempo delay synced to screen refresh
|
||||
repeat 32 {
|
||||
while c64.RASTER {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ graphics {
|
||||
|
||||
if dx >= dy {
|
||||
if positive_ix {
|
||||
forever {
|
||||
repeat {
|
||||
plot(y1)
|
||||
if plotx==x2
|
||||
return
|
||||
@ -51,7 +51,7 @@ graphics {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
forever {
|
||||
repeat {
|
||||
plot(y1)
|
||||
if plotx==x2
|
||||
return
|
||||
@ -66,7 +66,7 @@ graphics {
|
||||
}
|
||||
else {
|
||||
if positive_ix {
|
||||
forever {
|
||||
repeat {
|
||||
plot(y1)
|
||||
if y1 == y2
|
||||
return
|
||||
@ -78,7 +78,7 @@ graphics {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
forever {
|
||||
repeat {
|
||||
plot(y1)
|
||||
if y1 == y2
|
||||
return
|
||||
|
@ -105,16 +105,5 @@ main {
|
||||
c64scr.print("ok: 22 >= 22\n")
|
||||
else
|
||||
c64scr.print("error in 22>=22!\n")
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -105,16 +105,5 @@ main {
|
||||
c64scr.print("ok: -22.2 >= -22.2\n")
|
||||
else
|
||||
c64scr.print("error in -22.2>=-22.2!\n")
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -105,16 +105,5 @@ main {
|
||||
c64scr.print("ok: 22 >= 22\n")
|
||||
else
|
||||
c64scr.print("error in 22>=22!\n")
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -105,16 +105,5 @@ main {
|
||||
c64scr.print("ok: 322 >= 322\n")
|
||||
else
|
||||
c64scr.print("error in 322>=322!\n")
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -137,16 +137,5 @@ main {
|
||||
c64scr.print("ok: 1000 >= 1000\n")
|
||||
else
|
||||
c64scr.print("error in 1000>=1000!\n")
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -52,7 +52,6 @@ main {
|
||||
c64scr.print("v1=20, v2=-111\n")
|
||||
compare()
|
||||
|
||||
check_eval_stack()
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
@ -91,13 +90,4 @@ main {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -68,7 +68,6 @@ main {
|
||||
c64scr.print("v1 = v2 = 0\n")
|
||||
compare()
|
||||
|
||||
check_eval_stack()
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
@ -108,11 +107,4 @@ main {
|
||||
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,6 @@ main {
|
||||
c64scr.print("v1=220, v2=10\n")
|
||||
compare()
|
||||
|
||||
check_eval_stack()
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
@ -92,12 +91,4 @@ main {
|
||||
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -82,7 +82,6 @@ main {
|
||||
c64scr.print("v1 = v2 = aa\n")
|
||||
compare()
|
||||
|
||||
check_eval_stack()
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
@ -121,13 +120,4 @@ main {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -118,7 +118,6 @@ main {
|
||||
c64scr.print("v1 = v2 = aa\n")
|
||||
compare()
|
||||
|
||||
check_eval_stack()
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
@ -157,13 +156,4 @@ main {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Binary file not shown.
BIN
examples/compiled/mandelbrot-gfx.prg
Normal file
BIN
examples/compiled/mandelbrot-gfx.prg
Normal file
Binary file not shown.
Binary file not shown.
@ -19,7 +19,7 @@ main {
|
||||
|
||||
sub start() {
|
||||
float time=0.0
|
||||
forever {
|
||||
repeat {
|
||||
rotate_vertices(time)
|
||||
c64scr.clear_screenchars(32)
|
||||
draw_edges()
|
||||
|
@ -1,6 +1,7 @@
|
||||
%import c64lib
|
||||
%import c64utils
|
||||
|
||||
|
||||
spritedata $2000 {
|
||||
; this memory block contains the sprite data
|
||||
; it must start on an address aligned to 64 bytes.
|
||||
@ -81,7 +82,7 @@ main {
|
||||
uword anglex
|
||||
uword angley
|
||||
uword anglez
|
||||
forever {
|
||||
repeat {
|
||||
c64.TIME_LO=0
|
||||
rotate_vertices(msb(anglex), msb(angley), msb(anglez))
|
||||
position_sprites()
|
||||
|
@ -21,7 +21,7 @@ main {
|
||||
uword anglex
|
||||
uword angley
|
||||
uword anglez
|
||||
forever {
|
||||
repeat {
|
||||
rotate_vertices(msb(anglex), msb(angley), msb(anglez))
|
||||
c64scr.clear_screenchars(32)
|
||||
draw_edges()
|
||||
|
@ -6,7 +6,8 @@
|
||||
main {
|
||||
sub start() {
|
||||
c64scr.print("fibonacci sequence\n")
|
||||
for A in 0 to 20 {
|
||||
|
||||
repeat 21 {
|
||||
c64scr.print_uw(fib_next())
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
@ -42,17 +42,5 @@ main {
|
||||
c64.CHROUT('\n')
|
||||
|
||||
c64scr.print("bye!\n")
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
%import c64lib
|
||||
%import c64graphics
|
||||
|
||||
; TODO fix compiler errors when compiling without optimizations
|
||||
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
graphics.enable_bitmap_mode()
|
||||
draw_lines()
|
||||
draw_circles()
|
||||
forever {
|
||||
repeat {
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ main {
|
||||
ubyte y = y1
|
||||
|
||||
if dx >= dy {
|
||||
forever {
|
||||
repeat {
|
||||
c64scr.setcc(x, y, 42, 5)
|
||||
if x==x2
|
||||
return
|
||||
@ -89,7 +89,7 @@ main {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
forever {
|
||||
repeat {
|
||||
c64scr.setcc(x, y, 42, 5)
|
||||
if y == y2
|
||||
return
|
||||
|
54
examples/mandelbrot-gfx.p8
Normal file
54
examples/mandelbrot-gfx.p8
Normal file
@ -0,0 +1,54 @@
|
||||
%import c64lib
|
||||
%import c64flt
|
||||
%import c64graphics
|
||||
%zeropage floatsafe
|
||||
|
||||
; Draw a mandelbrot in graphics mode (the image will be 256 x 200 pixels).
|
||||
; NOTE: this will take an eternity to draw on a real c64.
|
||||
; even in Vice in warp mode (700% speed on my machine) it's slow, but you can see progress
|
||||
|
||||
; TODO fix compiler errors when compiling without optimizations
|
||||
|
||||
|
||||
main {
|
||||
const ubyte width = 255
|
||||
const ubyte height = 200
|
||||
const ubyte max_iter = 16
|
||||
|
||||
sub start() {
|
||||
graphics.enable_bitmap_mode()
|
||||
|
||||
ubyte pixelx
|
||||
ubyte pixely
|
||||
|
||||
for pixely in 0 to height-1 {
|
||||
float yy = (pixely as float)/0.4/height - 1.0
|
||||
|
||||
for pixelx in 0 to width-1 {
|
||||
float xx = (pixelx as float)/0.3/width - 2.2
|
||||
|
||||
float xsquared = 0.0
|
||||
float ysquared = 0.0
|
||||
float x = 0.0
|
||||
float y = 0.0
|
||||
ubyte iter = 0
|
||||
|
||||
while iter<max_iter and xsquared+ysquared<4.0 {
|
||||
y = x*y*2.0 + yy
|
||||
x = xsquared - ysquared + xx
|
||||
xsquared = x*x
|
||||
ysquared = y*y
|
||||
iter++
|
||||
}
|
||||
|
||||
if iter & 1 {
|
||||
graphics.plotx = pixelx
|
||||
graphics.plot(pixely)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repeat {
|
||||
}
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@ main {
|
||||
float y = 0.0
|
||||
ubyte iter = 0
|
||||
|
||||
while (iter<max_iter and xsquared+ysquared<4.0) {
|
||||
while iter<max_iter and xsquared+ysquared<4.0 {
|
||||
y = x*y*2.0 + yy
|
||||
x = xsquared - ysquared + xx
|
||||
xsquared = x*x
|
||||
@ -48,15 +48,5 @@ main {
|
||||
c64scr.print("finished in ")
|
||||
c64flt.print_f(duration)
|
||||
c64scr.print(" seconds!\n")
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -57,18 +57,6 @@ main {
|
||||
c64scr.print("Thanks for playing, ")
|
||||
c64scr.print(name)
|
||||
c64scr.print(".\n")
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ main {
|
||||
; calculate primes
|
||||
c64scr.print("prime numbers up to 255:\n\n")
|
||||
ubyte amount=0
|
||||
forever {
|
||||
repeat {
|
||||
ubyte prime = find_next_prime()
|
||||
if prime==0
|
||||
break
|
||||
@ -24,8 +24,6 @@ main {
|
||||
c64scr.print("number of primes (expected 54): ")
|
||||
c64scr.print_ub(amount)
|
||||
c64.CHROUT('\n')
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
|
||||
@ -48,14 +46,4 @@ main {
|
||||
}
|
||||
return candidate_prime
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ main {
|
||||
c64.SCROLY &= %11101111 ; blank the screen
|
||||
c64utils.set_rasterirq_excl(40) ; register exclusive raster irq handler
|
||||
|
||||
forever {
|
||||
repeat {
|
||||
; enjoy the moving bars :)
|
||||
}
|
||||
|
||||
|
@ -52,17 +52,5 @@ main {
|
||||
|
||||
c64flt.print_f(0.0)
|
||||
c64.CHROUT('\n')
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -34,17 +34,5 @@ main {
|
||||
c64scr.print("\nscreencode z=")
|
||||
c64scr.print_ub(c2)
|
||||
c64scr.print("\n")
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ main {
|
||||
c64scr.print("reversed\n")
|
||||
print_arrays()
|
||||
|
||||
check_eval_stack()
|
||||
return
|
||||
|
||||
|
||||
@ -65,14 +64,4 @@ main {
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,36 +0,0 @@
|
||||
%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"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
; TODO fix compiler errors when compiling without optimization ( /= )
|
||||
|
||||
|
||||
main {
|
||||
|
||||
struct Color {
|
||||
@ -11,7 +14,7 @@ main {
|
||||
|
||||
sub start() {
|
||||
|
||||
Color purple = {255, 0, 255}
|
||||
Color purple = [255, 0, 255]
|
||||
|
||||
Color other
|
||||
|
||||
@ -27,17 +30,5 @@ main {
|
||||
c64.CHROUT(',')
|
||||
c64scr.print_ub(other.blue)
|
||||
c64.CHROUT('\n')
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,11 +11,9 @@ main {
|
||||
float t
|
||||
ubyte color
|
||||
|
||||
forever {
|
||||
float x = sin(t)
|
||||
float y = cos(t*1.1356)
|
||||
ubyte xx=(x * width/2.2) + width/2.0 as ubyte
|
||||
ubyte yy=(y * height/2.2) + height/2.0 as ubyte
|
||||
repeat {
|
||||
ubyte xx=(sin(t) * width/2.2) + width/2.0 as ubyte
|
||||
ubyte yy=(cos(t*1.1356) * height/2.2) + height/2.0 as ubyte
|
||||
c64scr.setcc(xx, yy, 81, color)
|
||||
t += 0.08
|
||||
color++
|
||||
|
@ -15,7 +15,7 @@ main {
|
||||
|
||||
Ball ball
|
||||
|
||||
forever {
|
||||
repeat {
|
||||
ubyte x = msb(sin8u(msb(ball.anglex)) as uword * width)
|
||||
ubyte y = msb(cos8u(msb(ball.angley)) as uword * height)
|
||||
c64scr.setcc(x, y, 81, ball.color)
|
||||
|
@ -7,6 +7,7 @@
|
||||
; staged speed increase
|
||||
; some simple sound effects
|
||||
|
||||
|
||||
main {
|
||||
|
||||
const ubyte boardOffsetX = 14
|
||||
@ -38,8 +39,6 @@ newgame:
|
||||
spawnNextBlock()
|
||||
|
||||
waitkey:
|
||||
check_eval_stack()
|
||||
|
||||
if c64.TIME_LO>=(60-4*speedlevel) {
|
||||
c64.TIME_LO = 0
|
||||
|
||||
@ -107,6 +106,7 @@ waitkey:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if dropypos>ypos {
|
||||
ypos = dropypos
|
||||
sound.blockdrop()
|
||||
@ -389,16 +389,6 @@ waitkey:
|
||||
c64scr.setcc((i&3)+x, (i/4)+y, character, c)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -2,45 +2,45 @@
|
||||
%import c64utils
|
||||
%import c64flt
|
||||
%zeropage basicsafe
|
||||
%option enable_floats
|
||||
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
A += 50
|
||||
repeat 10 {
|
||||
c64.CHROUT('*')
|
||||
}
|
||||
c64.CHROUT('\n')
|
||||
|
||||
A += Y + 1
|
||||
A -= Y + 1
|
||||
A += Y - 1
|
||||
A -= Y - 1
|
||||
ubyte ub = 9
|
||||
repeat ub {
|
||||
c64.CHROUT('*')
|
||||
}
|
||||
c64.CHROUT('\n')
|
||||
|
||||
A += Y + 2
|
||||
A -= Y + 2
|
||||
A += Y - 2
|
||||
A -= Y - 2
|
||||
repeat 320 {
|
||||
c64.CHROUT('+')
|
||||
}
|
||||
c64.CHROUT('\n')
|
||||
|
||||
; 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
|
||||
uword uw = 320
|
||||
repeat uw {
|
||||
c64.CHROUT('-')
|
||||
}
|
||||
c64.CHROUT('\n')
|
||||
|
||||
ub = 7
|
||||
repeat ub+2 {
|
||||
c64.CHROUT('*')
|
||||
}
|
||||
c64.CHROUT('\n')
|
||||
|
||||
uw = 318
|
||||
repeat uw+2 {
|
||||
c64.CHROUT('*')
|
||||
}
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -29,6 +29,7 @@ main {
|
||||
&uword[4] muwarray = $c000
|
||||
&float[4] mflarray = $c000
|
||||
|
||||
ubyte A
|
||||
byte bb
|
||||
ubyte ub
|
||||
word ww
|
||||
|
@ -12,6 +12,7 @@ main {
|
||||
ubyte ub
|
||||
byte bb
|
||||
word total
|
||||
ubyte A
|
||||
|
||||
c64scr.plot(0,24)
|
||||
|
||||
@ -959,8 +960,6 @@ main {
|
||||
c64scr.print("ok\n")
|
||||
else
|
||||
c64scr.print("fail!!!\n")
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub wait_input() {
|
||||
@ -969,13 +968,4 @@ main {
|
||||
void c64scr.input_chars(input)
|
||||
c64scr.print("\n\n")
|
||||
}
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,25 +3,24 @@
|
||||
%import c64graphics
|
||||
%option enable_floats
|
||||
|
||||
; TODO fix compiler errors when compiling without optimizations
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
graphics.enable_bitmap_mode()
|
||||
turtle.init()
|
||||
; turtle.pu()
|
||||
; turtle.pos(150, 110)
|
||||
; turtle.pd()
|
||||
|
||||
ubyte i
|
||||
for i in 0 to 255 {
|
||||
while c64.RASTER {
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0 to 100 {
|
||||
turtle.fd(i+20)
|
||||
turtle.rt(94)
|
||||
}
|
||||
|
||||
forever {
|
||||
repeat {
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -42,26 +41,32 @@ turtle {
|
||||
c64.SPENA = 1
|
||||
c64.SP0COL = 5
|
||||
|
||||
turtlepos()
|
||||
update_turtle_sprite()
|
||||
}
|
||||
|
||||
sub turtlepos() {
|
||||
sub update_turtle_sprite() {
|
||||
uword xx = xpos as uword
|
||||
c64.SPXY[0] = lsb(xx) + 12
|
||||
if msb(xx)
|
||||
c64.MSIGX = 1
|
||||
else
|
||||
c64.MSIGX = 0
|
||||
c64.MSIGX = msb(xx) > 0
|
||||
c64.SPXY[1] = lsb(ypos) + 40
|
||||
}
|
||||
|
||||
sub pos(float x, float y) {
|
||||
if pendown {
|
||||
graphics.line(xpos as uword, ypos as ubyte, x as uword, y as ubyte)
|
||||
}
|
||||
xpos = x
|
||||
ypos = y
|
||||
update_turtle_sprite()
|
||||
}
|
||||
|
||||
sub fd(uword length) {
|
||||
float flen = length as float
|
||||
float sx = xpos
|
||||
float sy = ypos
|
||||
xpos += flen * sin(angle)
|
||||
ypos -= flen * cos(angle)
|
||||
turtlepos()
|
||||
update_turtle_sprite()
|
||||
if pendown {
|
||||
graphics.line(sx as uword, lsb(sy), xpos as uword, lsb(ypos))
|
||||
}
|
||||
|
@ -94,8 +94,8 @@ statement :
|
||||
| returnstmt
|
||||
| forloop
|
||||
| whileloop
|
||||
| untilloop
|
||||
| repeatloop
|
||||
| foreverloop
|
||||
| whenstmt
|
||||
| breakstmt
|
||||
| continuestmt
|
||||
@ -158,8 +158,7 @@ augassignment :
|
||||
;
|
||||
|
||||
assign_target:
|
||||
register
|
||||
| scoped_identifier
|
||||
scoped_identifier
|
||||
| arrayindexed
|
||||
| directmemory
|
||||
;
|
||||
@ -184,7 +183,6 @@ expression :
|
||||
| left = expression EOL? bop = 'xor' EOL? right = expression
|
||||
| prefix = 'not' expression
|
||||
| literalvalue
|
||||
| register
|
||||
| scoped_identifier
|
||||
| arrayindexed
|
||||
| directmemory
|
||||
@ -222,12 +220,6 @@ identifier : NAME ;
|
||||
|
||||
scoped_identifier : NAME ('.' NAME)* ;
|
||||
|
||||
register : 'A' | 'X' | 'Y' ;
|
||||
|
||||
registerorpair : 'A' | 'X' | 'Y' | 'AX' | 'AY' | 'XY' ; // pairs can only be used in subroutine params and returnvalues
|
||||
|
||||
statusregister : 'Pc' | 'Pz' | 'Pn' | 'Pv' ;
|
||||
|
||||
integerliteral : intpart=(DEC_INTEGER | HEX_INTEGER | BIN_INTEGER) wordsuffix? ;
|
||||
|
||||
wordsuffix : '.w' ;
|
||||
@ -236,8 +228,6 @@ booleanliteral : 'true' | 'false' ;
|
||||
|
||||
arrayliteral : '[' EOL? expression (',' EOL? expression)* EOL? ']' ; // you can split the values over several lines
|
||||
|
||||
structliteral : '{' EOL? expression (',' EOL? expression)* EOL? '}' ; // you can split the values over several lines
|
||||
|
||||
stringliteral : ALT_STRING_ENCODING? STRING ;
|
||||
|
||||
charliteral : ALT_STRING_ENCODING? SINGLECHAR ;
|
||||
@ -252,7 +242,6 @@ literalvalue :
|
||||
| stringliteral
|
||||
| charliteral
|
||||
| floatliteral
|
||||
| structliteral
|
||||
;
|
||||
|
||||
inlineasm : '%asm' INLINEASMBLOCK;
|
||||
@ -287,15 +276,15 @@ asmsub_decl : identifier '(' asmsub_params? ')' asmsub_clobbers? asmsub_returns?
|
||||
|
||||
asmsub_params : asmsub_param (',' EOL? asmsub_param)* ;
|
||||
|
||||
asmsub_param : vardecl '@' (registerorpair | statusregister | stack='stack') ;
|
||||
asmsub_param : vardecl '@' (identifier | stack='stack') ; // A,X,Y,AX,AY,XY,Pc,Pz,Pn,Pv allowed
|
||||
|
||||
asmsub_clobbers : 'clobbers' '(' clobber? ')' ;
|
||||
|
||||
clobber : register (',' register)* ;
|
||||
clobber : identifier (',' identifier)* ; // A,X,Y allowed
|
||||
|
||||
asmsub_returns : '->' asmsub_return (',' EOL? asmsub_return)* ;
|
||||
|
||||
asmsub_return : datatype '@' (registerorpair | statusregister | stack='stack') ;
|
||||
asmsub_return : datatype '@' (identifier | stack='stack') ; // A,X,Y,AX,AY,XY,Pc,Pz,Pn,Pv allowed
|
||||
|
||||
|
||||
if_stmt : 'if' expression EOL? (statement | statement_block) EOL? else_part? ; // statement is constrained later
|
||||
@ -308,13 +297,13 @@ branch_stmt : branchcondition EOL? (statement | statement_block) EOL? else_part?
|
||||
branchcondition: 'if_cs' | 'if_cc' | 'if_eq' | 'if_z' | 'if_ne' | 'if_nz' | 'if_pl' | 'if_pos' | 'if_mi' | 'if_neg' | 'if_vs' | 'if_vc' ;
|
||||
|
||||
|
||||
forloop : 'for' (register | identifier) 'in' expression EOL? (statement | statement_block) ;
|
||||
forloop : 'for' identifier 'in' expression EOL? (statement | statement_block) ;
|
||||
|
||||
whileloop: 'while' expression EOL? (statement | statement_block) ;
|
||||
|
||||
repeatloop: 'repeat' (statement | statement_block) EOL? 'until' expression ;
|
||||
untilloop: 'do' (statement | statement_block) EOL? 'until' expression ;
|
||||
|
||||
foreverloop: 'forever' EOL? (statement | statement_block) ;
|
||||
repeatloop: 'repeat' expression? EOL? (statement | statement_block) ;
|
||||
|
||||
whenstmt: 'when' expression '{' EOL (when_choice | EOL) * '}' EOL? ;
|
||||
|
||||
|
Reference in New Issue
Block a user