mirror of
https://github.com/irmen/prog8.git
synced 2025-06-13 20:23:46 +00:00
Compare commits
11 Commits
languageSe
...
master
Author | SHA1 | Date | |
---|---|---|---|
8dc2e47507 | |||
daf7c3357c | |||
e8795859c5 | |||
bebe60b687 | |||
ddceec364e | |||
d067fa4b73 | |||
b5e51ab937 | |||
552e55c29f | |||
a228908c1a | |||
15fc3b6c04 | |||
0456badd02 |
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@ -12,6 +12,7 @@
|
||||
<option name="pkg" value="" />
|
||||
<option name="language" value="Java" />
|
||||
<option name="generateListener" value="false" />
|
||||
<option name="generateVisitor" value="true" />
|
||||
</PerGrammarGenerationSettings>
|
||||
</list>
|
||||
</option>
|
||||
|
@ -657,7 +657,8 @@ class AsmGen6502Internal (
|
||||
}
|
||||
}
|
||||
expr.type.isFloat -> {
|
||||
require(options.compTarget.FLOAT_MEM_SIZE == 5) {"invalid float size ${expr.position}"}
|
||||
if(options.compTarget.FLOAT_MEM_SIZE != 5)
|
||||
TODO("support float size other than 5 ${expr.position}")
|
||||
assignExpressionToRegister(expr.index, RegisterOrPair.A)
|
||||
out("""
|
||||
sta P8ZP_SCRATCH_REG
|
||||
|
@ -420,6 +420,7 @@ $endLabel""")
|
||||
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||
precheckFromToWord(iterableDt, stepsize, varname, endLabel)
|
||||
asmgen.romableError("self-modifying code (forloop over words range)", stmt.position) // TODO fix romable; there is self-modifying code below
|
||||
asmgen.out("""
|
||||
sty $modifiedLabel+1
|
||||
sta $modifiedLabel2+1
|
||||
@ -442,7 +443,6 @@ $modifiedLabel sbc #0 ; modified
|
||||
eor #$80
|
||||
+ bpl $loopLabel
|
||||
$endLabel""")
|
||||
asmgen.romableError("self-modifying code (forloop over words range)", stmt.position) // TODO fix romable
|
||||
}
|
||||
|
||||
private fun precheckFromToWord(iterableDt: DataType, stepsize: Int, fromVar: String, endLabel: String) {
|
||||
|
@ -1740,6 +1740,17 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
}
|
||||
|
||||
fun requiresCmp(expr: PtExpression) =
|
||||
when (expr) {
|
||||
is PtFunctionCall -> {
|
||||
val function = asmgen.symbolTable.lookup(expr.name)
|
||||
function is StExtSub // don't assume the extsub/asmsub has set the cpu flags correctly on exit, add an explicit cmp
|
||||
}
|
||||
is PtBuiltinFunctionCall -> true
|
||||
is PtIfExpression -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
if(!expr.right.isSimple() && expr.operator!="xor") {
|
||||
// shortcircuit evaluation into A
|
||||
val shortcutLabel = asmgen.makeLabel("shortcut")
|
||||
@ -1747,15 +1758,23 @@ internal class AssignmentAsmGen(
|
||||
"and" -> {
|
||||
// short-circuit LEFT and RIGHT --> if LEFT then RIGHT else LEFT (== if !LEFT then LEFT else RIGHT)
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||
if(requiresCmp(expr.left))
|
||||
asmgen.out(" cmp #0")
|
||||
asmgen.out(" beq $shortcutLabel")
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.A, false)
|
||||
if(requiresCmp(expr.right))
|
||||
asmgen.out(" cmp #0")
|
||||
asmgen.out(shortcutLabel)
|
||||
}
|
||||
"or" -> {
|
||||
// short-circuit LEFT or RIGHT --> if LEFT then LEFT else RIGHT
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
|
||||
if(requiresCmp(expr.left))
|
||||
asmgen.out(" cmp #0")
|
||||
asmgen.out(" bne $shortcutLabel")
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.A, false)
|
||||
if(requiresCmp(expr.right))
|
||||
asmgen.out(" cmp #0")
|
||||
asmgen.out(shortcutLabel)
|
||||
}
|
||||
else -> throw AssemblyError("invalid logical operator")
|
||||
@ -2608,10 +2627,10 @@ $endLabel""")
|
||||
|
||||
private fun assignAddressOf(target: AsmAssignTarget, sourceName: String, msb: Boolean, arrayDt: DataType?, arrayIndexExpr: PtExpression?) {
|
||||
if(arrayIndexExpr!=null) {
|
||||
val arrayName = if(arrayDt!!.isSplitWordArray) sourceName+"_lsb" else sourceName // the _lsb split array comes first in memory
|
||||
val constIndex = arrayIndexExpr.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
if (arrayDt.isUnsignedWord) {
|
||||
if (arrayDt!!.isUnsignedWord) {
|
||||
// using a UWORD pointer with array indexing, always bytes
|
||||
require(!msb)
|
||||
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
|
||||
if(constIndex in 1..255)
|
||||
@ -2635,15 +2654,16 @@ $endLabel""")
|
||||
else {
|
||||
if(constIndex>0) {
|
||||
val offset = if(arrayDt.isSplitWordArray) constIndex else program.memsizer.memorySize(arrayDt, constIndex) // add arrayIndexExpr * elementsize to the address of the array variable.
|
||||
asmgen.out(" lda #<($arrayName + $offset) | ldy #>($arrayName + $offset)")
|
||||
asmgen.out(" lda #<($sourceName + $offset) | ldy #>($sourceName + $offset)")
|
||||
} else {
|
||||
asmgen.out(" lda #<$arrayName | ldy #>$arrayName")
|
||||
asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
|
||||
}
|
||||
}
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return
|
||||
} else {
|
||||
if (arrayDt.isUnsignedWord) {
|
||||
if (arrayDt!!.isUnsignedWord) {
|
||||
// using a UWORD pointer with array indexing, always bytes
|
||||
require(!msb)
|
||||
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
|
||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
@ -2678,10 +2698,29 @@ $endLabel""")
|
||||
}
|
||||
else {
|
||||
assignExpressionToRegister(arrayIndexExpr, RegisterOrPair.A, false)
|
||||
val subtype = arrayDt.sub!!
|
||||
if(subtype.isByteOrBool) {
|
||||
// elt size 1, we're good
|
||||
} else if(subtype.isWord) {
|
||||
if(!arrayDt.isSplitWordArray) {
|
||||
// elt size 2
|
||||
asmgen.out(" asl a")
|
||||
}
|
||||
} else if(subtype==BaseDataType.FLOAT) {
|
||||
if(asmgen.options.compTarget.FLOAT_MEM_SIZE != 5)
|
||||
TODO("support float size other than 5 ${arrayIndexExpr.position}")
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc P8ZP_SCRATCH_REG"""
|
||||
)
|
||||
} else throw AssemblyError("weird type $subtype")
|
||||
asmgen.out("""
|
||||
ldy #>$arrayName
|
||||
ldy #>$sourceName
|
||||
clc
|
||||
adc #<$arrayName
|
||||
adc #<$sourceName
|
||||
bcc +
|
||||
iny
|
||||
+""")
|
||||
|
@ -48,6 +48,36 @@ class IRUnusedCodeRemover(
|
||||
}
|
||||
|
||||
irprog.st.removeTree(blockLabel)
|
||||
removeBlockInits(irprog, blockLabel)
|
||||
}
|
||||
|
||||
private fun removeBlockInits(code: IRProgram, blockLabel: String) {
|
||||
val instructions = code.globalInits.instructions
|
||||
instructions.toTypedArray().forEach {ins ->
|
||||
if(ins.labelSymbol?.startsWith(blockLabel)==true) {
|
||||
instructions.remove(ins)
|
||||
}
|
||||
}
|
||||
|
||||
// remove stray loads
|
||||
instructions.toTypedArray().forEach { ins ->
|
||||
if(ins.opcode in arrayOf(Opcode.LOAD, Opcode.LOADR, Opcode.LOADM)) {
|
||||
if(ins.reg1!=0) {
|
||||
if(instructions.count { it.reg1==ins.reg1 || it.reg2==ins.reg1 } <2) {
|
||||
if(ins.labelSymbol!=null)
|
||||
code.st.removeIfExists(ins.labelSymbol!!)
|
||||
instructions.remove(ins)
|
||||
}
|
||||
}
|
||||
else if(ins.fpReg1!=0) {
|
||||
if (instructions.count { it.fpReg1 == ins.fpReg1 || it.fpReg2 == ins.fpReg1 } < 2) {
|
||||
if(ins.labelSymbol!=null)
|
||||
code.st.removeIfExists(ins.labelSymbol!!)
|
||||
instructions.remove(ins)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeUnusedSubroutines(): Int {
|
||||
|
@ -49,7 +49,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if(truepart.statements.singleOrNull() is Jump) {
|
||||
return listOf(
|
||||
IAstModification.InsertAfter(ifElse, elsepart, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse)
|
||||
IAstModification.ReplaceNode(elsepart, AnonymousScope.empty(), ifElse)
|
||||
)
|
||||
}
|
||||
if(elsepart.statements.singleOrNull() is Jump) {
|
||||
@ -57,7 +57,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
|
||||
IAstModification.InsertAfter(ifElse, truepart, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse),
|
||||
IAstModification.ReplaceNode(elsepart, AnonymousScope.empty(), ifElse),
|
||||
IAstModification.ReplaceNode(truepart, elsepart, ifElse)
|
||||
)
|
||||
}
|
||||
|
@ -82,12 +82,11 @@ class StatementOptimizer(private val program: Program,
|
||||
// empty true part? switch with the else part
|
||||
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isNotEmpty()) {
|
||||
val invertedCondition = invertCondition(ifElse.condition, program)
|
||||
val emptyscope = AnonymousScope(mutableListOf(), ifElse.elsepart.position)
|
||||
val truepart = AnonymousScope(ifElse.elsepart.statements, ifElse.truepart.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
|
||||
IAstModification.ReplaceNode(ifElse.truepart, truepart, ifElse),
|
||||
IAstModification.ReplaceNode(ifElse.elsepart, emptyscope, ifElse)
|
||||
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope.empty(), ifElse)
|
||||
)
|
||||
}
|
||||
|
||||
@ -106,7 +105,7 @@ class StatementOptimizer(private val program: Program,
|
||||
if(ifElse.truepart.statements.singleOrNull() is Return) {
|
||||
val elsePart = AnonymousScope(ifElse.elsepart.statements, ifElse.elsepart.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope(mutableListOf(), ifElse.elsepart.position), ifElse),
|
||||
IAstModification.ReplaceNode(ifElse.elsepart, AnonymousScope.empty(), ifElse),
|
||||
IAstModification.InsertAfter(ifElse, elsePart, parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
@ -146,7 +145,7 @@ class StatementOptimizer(private val program: Program,
|
||||
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)
|
||||
val scope = AnonymousScope.empty(forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
@ -161,7 +160,7 @@ class StatementOptimizer(private val program: Program,
|
||||
// loop over string of length 1 -> just assign the single character
|
||||
val character = options.compTarget.encodeString(sv.value, sv.encoding)[0]
|
||||
val byte = NumericLiteral(BaseDataType.UBYTE, character.toDouble(), iterable.position)
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
val scope = AnonymousScope.empty(forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
@ -173,7 +172,7 @@ class StatementOptimizer(private val program: Program,
|
||||
// loop over array of length 1 -> just assign the single value
|
||||
val av = (iterable.value as ArrayLiteral).value[0].constValue(program)?.number
|
||||
if(av!=null) {
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
val scope = AnonymousScope.empty(forLoop.position)
|
||||
scope.statements.add(Assignment(
|
||||
AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), NumericLiteral.optimalInteger(av.toInt(), iterable.position),
|
||||
AssignmentOrigin.OPTIMIZER, forLoop.position))
|
||||
@ -466,7 +465,7 @@ class StatementOptimizer(private val program: Program,
|
||||
return IfElse(
|
||||
compare,
|
||||
AnonymousScope(mutableListOf(assign), position),
|
||||
AnonymousScope(mutableListOf(), position),
|
||||
AnonymousScope.empty(),
|
||||
position
|
||||
)
|
||||
}
|
||||
|
@ -947,6 +947,18 @@ _no_msb_size
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
|
||||
%asm {{
|
||||
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
|
||||
cpx #0
|
||||
bne +
|
||||
dey
|
||||
+ dex
|
||||
tya
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pop() -> ubyte @A {
|
||||
%asm {{
|
||||
pla
|
||||
|
@ -969,6 +969,18 @@ _no_msb_size
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
|
||||
%asm {{
|
||||
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
|
||||
cpx #0
|
||||
bne +
|
||||
dey
|
||||
+ dex
|
||||
tya
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pop() -> ubyte @A {
|
||||
%asm {{
|
||||
pla
|
||||
|
@ -28,12 +28,20 @@
|
||||
; that routine can for instance call current() (or just look at the active_task variable) to get the id of the next task to execute.
|
||||
; It has then to return a boolean: true=next task is to be executed, false=skip the task this time.
|
||||
; - in tasks: call yield() to pass control to the next task. Use the returned userdata value to do different things.
|
||||
; For now, you MUST call yield() only from the actual subroutine that has been registered as a task!
|
||||
; (this is because otherwise the cpu call stack gets messed up and an RTS in task1 could suddenly pop a return address belonging to another tasks' call frame)
|
||||
; - call current() to get the current task id.
|
||||
; - call kill(taskid) to kill a task by id.
|
||||
; - call killall() to kill all tasks.
|
||||
; - IMPORTANT: if you add the same subroutine multiple times, IT CANNOT DEPEND ON ANY LOCAL VARIABLES OR R0-R15 TO KEEP STATE. NOT EVEN REPEAT LOOP COUNTERS.
|
||||
; Those are all shared in the different tasks! You HAVE to use a mechanism around the userdata value (pointer?) to keep separate state elsewhere!
|
||||
; - IMPORTANT: ``defer`` cannot be used inside a coroutine that is reused for multiple tasks!!!
|
||||
;
|
||||
; TIP: HOW TO WAIT without BLOCKING other coroutines?
|
||||
; Make sure you call yield() in the waiting loop, for example:
|
||||
; uword timer = cbm.RDTIM16() + 60
|
||||
; while cbm.RDTIM16() != timer
|
||||
; void coroutines.yield()
|
||||
|
||||
coroutines {
|
||||
%option ignore_unused
|
||||
@ -41,19 +49,17 @@ coroutines {
|
||||
const ubyte MAX_TASKS = 64
|
||||
uword[MAX_TASKS] tasklist
|
||||
uword[MAX_TASKS] userdatas
|
||||
uword[MAX_TASKS] returnaddresses
|
||||
ubyte active_task
|
||||
uword supervisor
|
||||
|
||||
sub add(uword taskaddress, uword userdata) -> ubyte {
|
||||
sub add(uword @nozp taskaddress, uword @nozp userdata) -> ubyte {
|
||||
; find the next empty slot in the tasklist and stick it there
|
||||
; returns the task id of the new task, or 255 if there was no space for more tasks. 0 is a valid task id!
|
||||
; also returns the success in the Carry flag (carry set=success, carry clear = task was not added)
|
||||
for cx16.r0L in 0 to len(tasklist)-1 {
|
||||
if tasklist[cx16.r0L] == 0 {
|
||||
tasklist[cx16.r0L] = taskaddress
|
||||
tasklist[cx16.r0L] = sys.get_as_returnaddress(taskaddress)
|
||||
userdatas[cx16.r0L] = userdata
|
||||
returnaddresses[cx16.r0L] = 0
|
||||
sys.set_carry()
|
||||
return cx16.r0L
|
||||
}
|
||||
@ -70,57 +76,48 @@ coroutines {
|
||||
}
|
||||
}
|
||||
|
||||
sub run(uword supervisor_routine) {
|
||||
sub run(uword @nozp supervisor_routine) {
|
||||
supervisor = supervisor_routine
|
||||
for active_task in 0 to len(tasklist)-1 {
|
||||
if tasklist[active_task]!=0 {
|
||||
; activate the termination handler and start the first task
|
||||
; note: cannot use pushw() because JSR doesn't push the return address in the same way
|
||||
sys.push_returnaddress(&termination)
|
||||
goto tasklist[active_task]
|
||||
sys.pushw(tasklist[active_task])
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub yield() -> uword {
|
||||
; Store the return address of the yielding task,
|
||||
; and continue with the next one instead (round-robin)
|
||||
; Returns the associated userdata value
|
||||
uword task_start, task_continue
|
||||
returnaddresses[active_task] = sys.popw()
|
||||
; Store the return address of the yielding task, and continue with the next one instead (round-robin)
|
||||
; Returns the associated userdata value.
|
||||
; NOTE: CAN ONLY BE CALLED FROM THE SCOPE OF THE SUBROUTINE THAT HAS BEEN REGISTERED AS THE TASK!
|
||||
uword task_return_address
|
||||
tasklist[active_task] = sys.popw()
|
||||
|
||||
resume_with_next_task:
|
||||
skip_task:
|
||||
if not next_task() {
|
||||
void sys.popw() ; remove return to the termination handler
|
||||
return 0 ; exiting here will now actually return from the start() call back to the calling program :)
|
||||
return 0 ; exiting here will now actually return back to the calling program that called run()
|
||||
}
|
||||
|
||||
if supervisor!=0 {
|
||||
if supervisor!=0
|
||||
if lsb(call(supervisor))==0
|
||||
goto resume_with_next_task
|
||||
}
|
||||
goto skip_task
|
||||
|
||||
if task_continue==0 {
|
||||
; fetch start address of next task.
|
||||
; address on the stack must be pushed in reverse byte order
|
||||
; also, subtract 1 from the start address because JSR pushes returnaddress minus 1
|
||||
; note: cannot use pushw() because JSR doesn't push the return address in the same way
|
||||
sys.push_returnaddress(task_start)
|
||||
} else
|
||||
sys.pushw(task_continue)
|
||||
|
||||
; returning from yield then continues with the next coroutine
|
||||
; returning from yield then continues with the next coroutine:
|
||||
sys.pushw(task_return_address)
|
||||
return userdatas[active_task]
|
||||
|
||||
sub next_task() -> bool {
|
||||
; search through the task list for the next active task
|
||||
repeat len(tasklist) {
|
||||
active_task++
|
||||
if active_task==len(returnaddresses)
|
||||
if active_task==len(tasklist)
|
||||
active_task=0
|
||||
task_start = tasklist[active_task]
|
||||
if task_start!=0 {
|
||||
task_continue = returnaddresses[active_task]
|
||||
task_return_address = tasklist[active_task]
|
||||
if task_return_address!=0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -128,9 +125,8 @@ resume_with_next_task:
|
||||
}
|
||||
}
|
||||
|
||||
sub kill(ubyte taskid) {
|
||||
sub kill(ubyte @nozp taskid) {
|
||||
tasklist[taskid] = 0
|
||||
returnaddresses[taskid] = 0
|
||||
}
|
||||
|
||||
sub current() -> ubyte {
|
||||
@ -138,12 +134,10 @@ resume_with_next_task:
|
||||
}
|
||||
|
||||
sub termination() {
|
||||
; a task has terminated. wipe it from the list.
|
||||
; this is an internal routine
|
||||
; internal routine: a task has terminated. wipe it from the list.
|
||||
kill(active_task)
|
||||
; reactivate this termination handler
|
||||
; note: cannot use pushw() because JSR doesn't push the return address in the same way
|
||||
; reactivate this termination handler and go to the next task
|
||||
sys.push_returnaddress(&termination)
|
||||
goto coroutines.yield.resume_with_next_task
|
||||
goto coroutines.yield.skip_task
|
||||
}
|
||||
}
|
||||
|
@ -2028,6 +2028,18 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
|
||||
%asm {{
|
||||
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
|
||||
cpx #0
|
||||
bne +
|
||||
dey
|
||||
+ dex
|
||||
tya
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pop() -> ubyte @A {
|
||||
%asm {{
|
||||
pla
|
||||
|
@ -482,6 +482,18 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
|
||||
%asm {{
|
||||
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
|
||||
cpx #0
|
||||
bne +
|
||||
dey
|
||||
+ dex
|
||||
tya
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pop() -> ubyte @A {
|
||||
%asm {{
|
||||
pla
|
||||
|
@ -43,49 +43,72 @@ _done
|
||||
}}
|
||||
}
|
||||
|
||||
/*
|
||||
prog8 source code for the above routine:
|
||||
|
||||
sub gnomesort_ub(uword @requirezp values, ubyte num_elements) {
|
||||
sub gnomesort_by_ub(uword @requirezp uw_keys, uword values, ubyte num_elements) {
|
||||
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
|
||||
ubyte @zp pos=1
|
||||
while pos != num_elements {
|
||||
if values[pos]>=values[pos-1]
|
||||
if uw_keys[pos]>=uw_keys[pos-1]
|
||||
pos++
|
||||
else {
|
||||
; swap elements
|
||||
cx16.r0L = values[pos-1]
|
||||
values[pos-1] = values[pos]
|
||||
values[pos] = cx16.r0L
|
||||
cx16.r0L = uw_keys[pos-1]
|
||||
uw_keys[pos-1] = uw_keys[pos]
|
||||
uw_keys[pos] = cx16.r0L
|
||||
uword @requirezp vptr = values + pos*$0002 -2
|
||||
cx16.r0 = peekw(vptr)
|
||||
pokew(vptr, peekw(vptr+2))
|
||||
pokew(vptr+2, cx16.r0)
|
||||
|
||||
pos--
|
||||
if_z
|
||||
pos++
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
sub gnomesort_uw(uword values, ubyte num_elements) {
|
||||
; When written in asm this is 10-20% faster, but unreadable. Not worth it.
|
||||
; Also, sorting just an array of word numbers is very seldomly used, most often you
|
||||
; need to sort other things associated with it as well and that is not done here anyway,
|
||||
; so requires a custom user coded sorting routine anyway.
|
||||
ubyte @zp pos = 1
|
||||
uword @requirezp ptr = values+2
|
||||
sub gnomesort_uw(uword @requirezp values, ubyte num_elements) {
|
||||
; Sorts the values array (no-split unsigned words).
|
||||
; Max number of elements is 128. Clobbers R0 and R1.
|
||||
ubyte @zp pos=2
|
||||
num_elements *= 2
|
||||
while pos != num_elements {
|
||||
cx16.r0 = peekw(ptr-2)
|
||||
cx16.r1 = peekw(ptr)
|
||||
if cx16.r0<=cx16.r1 {
|
||||
pos++
|
||||
ptr+=2
|
||||
}
|
||||
cx16.r1L = pos-2
|
||||
if peekw(values+pos) >= peekw(values + cx16.r1L)
|
||||
pos += 2
|
||||
else {
|
||||
; swap elements
|
||||
pokew(ptr-2, cx16.r1)
|
||||
pokew(ptr, cx16.r0)
|
||||
if pos>1 {
|
||||
pos--
|
||||
ptr-=2
|
||||
}
|
||||
cx16.r0 = peekw(values + cx16.r1L)
|
||||
pokew(values + cx16.r1L, peekw(values + pos))
|
||||
pokew(values + pos, cx16.r0)
|
||||
pos-=2
|
||||
if_z
|
||||
pos+=2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub gnomesort_by_uw(uword @requirezp uw_keys, uword wordvalues, ubyte num_elements) {
|
||||
; Sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
|
||||
; both arrays should be no-split array of words. uw_keys are unsigned.
|
||||
; Max number of elements is 128. Clobbers R0 and R1.
|
||||
ubyte @zp pos=2
|
||||
num_elements *= 2
|
||||
while pos != num_elements {
|
||||
cx16.r1L = pos-2
|
||||
if peekw(uw_keys+pos) >= peekw(uw_keys + cx16.r1L)
|
||||
pos += 2
|
||||
else {
|
||||
; swap elements
|
||||
cx16.r0 = peekw(uw_keys + cx16.r1L)
|
||||
pokew(uw_keys + cx16.r1L, peekw(uw_keys+ pos))
|
||||
pokew(uw_keys + pos, cx16.r0)
|
||||
cx16.r0 = peekw(wordvalues + cx16.r1L)
|
||||
pokew(wordvalues + cx16.r1L, peekw(wordvalues + pos))
|
||||
pokew(wordvalues + pos, cx16.r0)
|
||||
|
||||
pos-=2
|
||||
if_z
|
||||
pos+=2
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -93,6 +116,7 @@ _done
|
||||
; gnomesort_pointers is not worth it over shellshort_pointers.
|
||||
|
||||
sub shellsort_ub(uword @requirezp values, ubyte num_elements) {
|
||||
; sorts the values array (unsigned bytes).
|
||||
num_elements--
|
||||
ubyte @zp gap
|
||||
for gap in [132, 57, 23, 10, 4, 1] {
|
||||
@ -115,6 +139,7 @@ _done
|
||||
}
|
||||
|
||||
sub shellsort_uw(uword @requirezp values, ubyte num_elements) {
|
||||
; sorts the values array (no-split unsigned words).
|
||||
num_elements--
|
||||
ubyte gap
|
||||
for gap in [132, 57, 23, 10, 4, 1] {
|
||||
@ -124,13 +149,65 @@ _done
|
||||
ubyte @zp j = i
|
||||
ubyte @zp k = j-gap
|
||||
while j>=gap {
|
||||
uword @zp v = peekw(values+k*2)
|
||||
uword @zp v = peekw(values+k*$0002)
|
||||
if v <= temp break
|
||||
pokew(values+j*2, v)
|
||||
pokew(values+j*$0002, v)
|
||||
j = k
|
||||
k -= gap
|
||||
}
|
||||
pokew(values+j*2, temp)
|
||||
pokew(values+j*$0002, temp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub shellsort_by_ub(uword @requirezp ub_keys, uword @requirezp wordvalues, ubyte num_elements) {
|
||||
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
|
||||
num_elements--
|
||||
ubyte @zp gap
|
||||
for gap in [132, 57, 23, 10, 4, 1] {
|
||||
ubyte i
|
||||
for i in gap to num_elements {
|
||||
ubyte @zp temp = ub_keys[i]
|
||||
uword temp_wv = peekw(wordvalues + i*$0002)
|
||||
ubyte @zp j = i
|
||||
ubyte @zp k = j-gap
|
||||
repeat {
|
||||
ubyte @zp v = ub_keys[k]
|
||||
if v <= temp break
|
||||
if j < gap break
|
||||
ub_keys[j] = v
|
||||
pokew(wordvalues + j*$0002, peekw(wordvalues + k*$0002))
|
||||
j = k
|
||||
k -= gap
|
||||
}
|
||||
ub_keys[j] = temp
|
||||
pokew(wordvalues + j*$0002, temp_wv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub shellsort_by_uw(uword @requirezp uw_keys, uword @requirezp wordvalues, ubyte num_elements) {
|
||||
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
|
||||
; both arrays should be no-split array of words. uw_keys are unsigned.
|
||||
num_elements--
|
||||
ubyte gap
|
||||
for gap in [132, 57, 23, 10, 4, 1] {
|
||||
ubyte i
|
||||
for i in gap to num_elements {
|
||||
uword @zp temp = peekw(uw_keys+i*$0002)
|
||||
uword temp_wv = peekw(wordvalues + i*$0002)
|
||||
ubyte @zp j = i
|
||||
ubyte @zp k = j-gap
|
||||
while j>=gap {
|
||||
uword @zp v = peekw(uw_keys+k*2)
|
||||
if v <= temp break
|
||||
pokew(uw_keys+j*2, v)
|
||||
pokew(wordvalues + j*$0002, peekw(wordvalues + k*$0002))
|
||||
j = k
|
||||
k -= gap
|
||||
}
|
||||
pokew(uw_keys+j*2, temp)
|
||||
pokew(wordvalues + j*$0002, temp_wv)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -147,14 +224,14 @@ _done
|
||||
ubyte @zp j = i
|
||||
ubyte @zp k = j-gap
|
||||
while j>=gap {
|
||||
cx16.r0 = peekw(pointers+k*2)
|
||||
cx16.r0 = peekw(pointers+k*$0002)
|
||||
void call(comparefunc)
|
||||
if_cs break
|
||||
pokew(pointers+j*2, cx16.r0)
|
||||
pokew(pointers+j*$0002, cx16.r0)
|
||||
j = k
|
||||
k -= gap
|
||||
}
|
||||
pokew(pointers+j*2, cx16.r1)
|
||||
pokew(pointers+j*$0002, cx16.r1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -151,6 +151,33 @@ _found tya
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub find_eol(uword string @AY) -> ubyte @A, bool @Pc {
|
||||
; Locates the position of the first End Of Line character in the string.
|
||||
; This is a convenience function that looks for both a CR or LF (byte 13 or byte 10) as being a possible Line Ending.
|
||||
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
|
||||
%asm {{
|
||||
; need to copy the the cx16 virtual registers to zeropage to make this run on C64...
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq _notfound
|
||||
cmp #13
|
||||
beq _found
|
||||
cmp #10
|
||||
beq _found
|
||||
iny
|
||||
bne -
|
||||
_notfound lda #255
|
||||
clc
|
||||
rts
|
||||
_found tya
|
||||
sec
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub rfind(uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc {
|
||||
; Locates the first position of the given character in the string, starting from the right.
|
||||
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
|
||||
|
103
compiler/res/prog8lib/virtual/sorting.p8
Normal file
103
compiler/res/prog8lib/virtual/sorting.p8
Normal file
@ -0,0 +1,103 @@
|
||||
; **experimental** data sorting routines, API subject to change!!
|
||||
|
||||
; NOTE: gnomesort is not implemented here, just use shellshort.
|
||||
|
||||
sorting {
|
||||
%option ignore_unused
|
||||
|
||||
sub shellsort_ub(uword @requirezp values, ubyte num_elements) {
|
||||
num_elements--
|
||||
ubyte @zp gap
|
||||
for gap in [132, 57, 23, 10, 4, 1] {
|
||||
ubyte i
|
||||
for i in gap to num_elements {
|
||||
ubyte @zp temp = values[i]
|
||||
ubyte @zp j = i
|
||||
ubyte @zp k = j-gap
|
||||
repeat {
|
||||
ubyte @zp v = values[k]
|
||||
if v <= temp break
|
||||
if j < gap break
|
||||
values[j] = v
|
||||
j = k
|
||||
k -= gap
|
||||
}
|
||||
values[j] = temp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub shellsort_uw(uword @requirezp values, ubyte num_elements) {
|
||||
num_elements--
|
||||
ubyte gap
|
||||
for gap in [132, 57, 23, 10, 4, 1] {
|
||||
ubyte i
|
||||
for i in gap to num_elements {
|
||||
uword @zp temp = peekw(values+i*$0002)
|
||||
ubyte @zp j = i
|
||||
ubyte @zp k = j-gap
|
||||
while j>=gap {
|
||||
uword @zp v = peekw(values+k*2)
|
||||
if v <= temp break
|
||||
pokew(values+j*2, v)
|
||||
j = k
|
||||
k -= gap
|
||||
}
|
||||
pokew(values+j*2, temp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub shellsort_by_ub(uword @requirezp ub_keys, uword @requirezp wordvalues, ubyte num_elements) {
|
||||
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
|
||||
num_elements--
|
||||
ubyte @zp gap
|
||||
for gap in [132, 57, 23, 10, 4, 1] {
|
||||
ubyte i
|
||||
for i in gap to num_elements {
|
||||
ubyte @zp temp = ub_keys[i]
|
||||
uword temp_wv = peekw(wordvalues + i*$0002)
|
||||
ubyte @zp j = i
|
||||
ubyte @zp k = j-gap
|
||||
repeat {
|
||||
ubyte @zp v = ub_keys[k]
|
||||
if v <= temp break
|
||||
if j < gap break
|
||||
ub_keys[j] = v
|
||||
pokew(wordvalues + j*$0002, peekw(wordvalues + k*$0002))
|
||||
j = k
|
||||
k -= gap
|
||||
}
|
||||
ub_keys[j] = temp
|
||||
pokew(wordvalues + j*$0002, temp_wv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub shellsort_by_uw(uword @requirezp uw_keys, uword @requirezp wordvalues, ubyte num_elements) {
|
||||
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
|
||||
; both arrays should be no-split array of words. uw_keys are unsigned.
|
||||
num_elements--
|
||||
ubyte gap
|
||||
for gap in [132, 57, 23, 10, 4, 1] {
|
||||
ubyte i
|
||||
for i in gap to num_elements {
|
||||
uword @zp temp = peekw(uw_keys+i*$0002)
|
||||
uword temp_wv = peekw(wordvalues + i*$0002)
|
||||
ubyte @zp j = i
|
||||
ubyte @zp k = j-gap
|
||||
while j>=gap {
|
||||
uword @zp v = peekw(uw_keys+k*2)
|
||||
if v <= temp break
|
||||
pokew(uw_keys+j*2, v)
|
||||
pokew(wordvalues + j*$0002, peekw(wordvalues + k*$0002))
|
||||
j = k
|
||||
k -= gap
|
||||
}
|
||||
pokew(uw_keys+j*2, temp)
|
||||
pokew(wordvalues + j*$0002, temp_wv)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -53,34 +53,47 @@ strings {
|
||||
target[ix]=0
|
||||
}
|
||||
|
||||
sub find(str st, ubyte character) -> ubyte {
|
||||
sub find(str st, ubyte character) -> ubyte, bool {
|
||||
; Locates the first position of the given character in the string,
|
||||
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
|
||||
; NOTE: because this isn't an asmsub, there's only a SINGLE return value here. On the c64/cx16 targets etc there are 2 return values.
|
||||
; returns index in A, and boolean if found or not. (when false A will also be 255, an invalid index).
|
||||
ubyte ix
|
||||
for ix in 0 to length(st)-1 {
|
||||
if st[ix]==character {
|
||||
sys.set_carry()
|
||||
return ix
|
||||
return ix, true
|
||||
}
|
||||
}
|
||||
sys.clear_carry()
|
||||
return 255
|
||||
return 255, false
|
||||
}
|
||||
|
||||
sub rfind(uword stringptr, ubyte character) -> ubyte {
|
||||
sub find_eol(str st) -> ubyte, bool {
|
||||
; Locates the position of the first End Of Line character in the string.
|
||||
; This is a convenience function that looks for both a CR or LF (byte 13 or byte 10) as being a possible Line Ending.
|
||||
; returns index in A, and boolean if found or not. (when false A will also be 255, an invalid index).
|
||||
ubyte ix
|
||||
for ix in 0 to length(st)-1 {
|
||||
if st[ix] in "\x0a\x0d" {
|
||||
sys.set_carry()
|
||||
return ix, true
|
||||
}
|
||||
}
|
||||
sys.clear_carry()
|
||||
return 255, false
|
||||
}
|
||||
|
||||
sub rfind(uword stringptr, ubyte character) -> ubyte, bool {
|
||||
; Locates the first position of the given character in the string, starting from the right.
|
||||
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
|
||||
; NOTE: because this isn't an asmsub, there's only a SINGLE return value here. On the c64/cx16 targets etc there are 2 return values.
|
||||
; returns index in A, and boolean if found or not. (when false A will also be 255, an invalid index).
|
||||
ubyte ix
|
||||
for ix in length(stringptr)-1 downto 0 {
|
||||
if stringptr[ix]==character {
|
||||
sys.set_carry()
|
||||
return ix
|
||||
return ix, true
|
||||
}
|
||||
}
|
||||
sys.clear_carry()
|
||||
return 255
|
||||
return 255, false
|
||||
}
|
||||
|
||||
sub contains(str st, ubyte character) -> bool {
|
||||
|
@ -199,6 +199,12 @@ sys {
|
||||
}}
|
||||
}
|
||||
|
||||
sub get_as_returnaddress(uword address) -> uword {
|
||||
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
|
||||
address--
|
||||
return mkword(lsb(address), msb(address))
|
||||
}
|
||||
|
||||
sub pop() -> ubyte {
|
||||
; note: this *should* be inlined, however since the VM has separate program counter and value stacks, this also works
|
||||
%ir {{
|
||||
|
@ -26,6 +26,7 @@ import kotlin.io.path.isRegularFile
|
||||
import kotlin.io.path.nameWithoutExtension
|
||||
import kotlin.system.exitProcess
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.measureTime
|
||||
import kotlin.time.measureTimedValue
|
||||
|
||||
@ -228,19 +229,20 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
System.err.flush()
|
||||
|
||||
if(!args.quietAll && args.showTimings) {
|
||||
println("\n**** TIMING ****")
|
||||
println("source parsing : ${parseDuration}")
|
||||
println("ast processing : ${processDuration}")
|
||||
println("ast optimizing : ${optimizeDuration}")
|
||||
println("ast postprocess : ${postprocessDuration}")
|
||||
println("code prepare : ${simplifiedAstDuration}")
|
||||
println("code generation : ${createAssemblyDuration}")
|
||||
println(" total : ${parseDuration + processDuration + optimizeDuration + postprocessDuration + simplifiedAstDuration + createAssemblyDuration}")
|
||||
println("\n**** TIMINGS ****")
|
||||
println("source parsing : ${parseDuration.toString(DurationUnit.SECONDS, 3)}")
|
||||
println("ast processing : ${processDuration.toString(DurationUnit.SECONDS, 3)}")
|
||||
println("ast optimizing : ${optimizeDuration.toString(DurationUnit.SECONDS, 3)}")
|
||||
println("ast postprocess : ${postprocessDuration.toString(DurationUnit.SECONDS, 3)}")
|
||||
println("code prepare : ${simplifiedAstDuration.toString(DurationUnit.SECONDS, 3)}")
|
||||
println("code generation : ${createAssemblyDuration.toString(DurationUnit.SECONDS, 3)}")
|
||||
val totalDuration = parseDuration + processDuration + optimizeDuration + postprocessDuration + simplifiedAstDuration + createAssemblyDuration
|
||||
println(" total : ${totalDuration.toString(DurationUnit.SECONDS, 3)}")
|
||||
}
|
||||
}
|
||||
|
||||
if(!args.quietAll) {
|
||||
println("\nTotal compilation+assemble time: ${totalTime}.")
|
||||
println("\nTotal compilation+assemble time: ${totalTime.toString(DurationUnit.SECONDS, 3)}.")
|
||||
}
|
||||
return CompilationResult(resultingProgram!!, ast, compilationOptions, importedFiles)
|
||||
} catch (px: ParseError) {
|
||||
|
@ -184,8 +184,6 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
val iterableDt = forLoop.iterable.inferType(program).getOrUndef()
|
||||
|
||||
if(iterableDt.isNumeric) TODO("iterable type should not be simple numeric "+forLoop.position)
|
||||
|
||||
if(forLoop.iterable is IFunctionCall) {
|
||||
errors.err("can not loop over function call return value", forLoop.position)
|
||||
} else if(!(iterableDt.isIterable) && forLoop.iterable !is RangeExpression) {
|
||||
@ -408,11 +406,42 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
// subroutine must contain at least one 'return' or 'goto'
|
||||
// (or if it has an asm block, that must contain a 'rts' or 'jmp' or 'bra')
|
||||
var haveReturnError = false
|
||||
if(!hasReturnOrExternalJumpOrRts(subroutine)) {
|
||||
if (subroutine.returntypes.isNotEmpty()) {
|
||||
// for asm subroutines with an address, no statement check is possible.
|
||||
if (subroutine.asmAddress == null && !subroutine.inline)
|
||||
if (subroutine.asmAddress == null && !subroutine.inline) {
|
||||
err("non-inline subroutine has result value(s) and thus must have at least one 'return' or external 'goto' in it (or the assembler equivalent in case of %asm)")
|
||||
haveReturnError = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val lastStatement = subroutine.statements.reversed().dropWhile { it is Subroutine || it is VarDecl || it is Directive || (it is Assignment && it.origin== AssignmentOrigin.VARINIT) }.firstOrNull()
|
||||
if(!haveReturnError && !subroutine.isAsmSubroutine && !subroutine.inline && subroutine.returntypes.isNotEmpty() && lastStatement !is Return) {
|
||||
if(lastStatement==null)
|
||||
err("subroutine '${subroutine.name}' has result value(s) but doesn't end with a return statement")
|
||||
else {
|
||||
val returnError = when (lastStatement) {
|
||||
is Jump, is OnGoto -> false
|
||||
is IStatementContainer -> !hasReturnOrExternalJumpOrRts(lastStatement as IStatementContainer)
|
||||
is InlineAssembly -> !lastStatement.hasReturnOrRts()
|
||||
is ForLoop -> hasReturnOrExternalJumpOrRts(lastStatement.body)
|
||||
is IfElse -> !hasReturnOrExternalJumpOrRts(lastStatement.truepart) && !hasReturnOrExternalJumpOrRts(lastStatement.elsepart)
|
||||
is ConditionalBranch -> !hasReturnOrExternalJumpOrRts(lastStatement.truepart) && !hasReturnOrExternalJumpOrRts(lastStatement.elsepart)
|
||||
is RepeatLoop -> {
|
||||
lastStatement.iterations!=null || !hasReturnOrExternalJumpOrRts(lastStatement.body)
|
||||
}
|
||||
is UntilLoop -> !hasReturnOrExternalJumpOrRts(lastStatement.body)
|
||||
is WhileLoop -> !hasReturnOrExternalJumpOrRts(lastStatement.body)
|
||||
is When -> lastStatement.choices.all { !hasReturnOrExternalJumpOrRts(it.statements) }
|
||||
else -> true
|
||||
}
|
||||
|
||||
if(returnError) {
|
||||
val pos = if(lastStatement is Subroutine) subroutine.position else lastStatement.position
|
||||
errors.err("subroutine '${subroutine.name}' has result value(s) but doesn't end with a return statement", pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ internal class BeforeAsmAstChanger(val program: Program, private val options: Co
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
|
||||
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
|
||||
// and if an assembly block doesn't contain a rts/rti.
|
||||
// and if an assembly block doesn't contain a rts/rti. AND if there's no return value(s) because we can't make one up!
|
||||
if (!subroutine.isAsmSubroutine) {
|
||||
if(subroutine.isEmpty()) {
|
||||
if(subroutine.returntypes.isNotEmpty())
|
||||
|
@ -104,7 +104,7 @@ if not CONDITION
|
||||
untilLoop.body,
|
||||
IfElse(invertCondition(untilLoop.condition, program),
|
||||
AnonymousScope(mutableListOf(program.jumpLabel(loopLabel)), pos),
|
||||
AnonymousScope(mutableListOf(), pos),
|
||||
AnonymousScope.empty(),
|
||||
pos)
|
||||
), pos)
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop, replacement, parent))
|
||||
@ -148,7 +148,7 @@ _after:
|
||||
loopLabel,
|
||||
IfElse(invertCondition(whileLoop.condition, program),
|
||||
AnonymousScope(mutableListOf(program.jumpLabel(afterLabel)), pos),
|
||||
AnonymousScope(mutableListOf(), pos),
|
||||
AnonymousScope.empty(),
|
||||
pos),
|
||||
whileLoop.body,
|
||||
program.jumpLabel(loopLabel),
|
||||
@ -380,7 +380,7 @@ _after:
|
||||
}
|
||||
|
||||
val callTarget = ArrayIndexedExpression(IdentifierReference(listOf(jumplistArray.name), jumplistArray.position), ArrayIndex(indexValue.copy(), indexValue.position), ongoto.position)
|
||||
val callIndexed = AnonymousScope(mutableListOf(), ongoto.position)
|
||||
val callIndexed = AnonymousScope.empty(ongoto.position)
|
||||
if(ongoto.isCall) {
|
||||
callIndexed.statements.add(FunctionCallStatement(IdentifierReference(listOf("call"), ongoto.position), mutableListOf(callTarget), true, ongoto.position))
|
||||
} else {
|
||||
@ -390,7 +390,7 @@ _after:
|
||||
val ifSt = if(ongoto.elsepart==null || ongoto.elsepart!!.isEmpty()) {
|
||||
// if index<numlabels call(labels[index])
|
||||
val compare = BinaryExpression(indexValue.copy(), "<", NumericLiteral.optimalInteger(numlabels, ongoto.position), ongoto.position)
|
||||
IfElse(compare, callIndexed, AnonymousScope(mutableListOf(), ongoto.position), ongoto.position)
|
||||
IfElse(compare, callIndexed, AnonymousScope.empty(), ongoto.position)
|
||||
} else {
|
||||
// if index>=numlabels elselabel() else call(labels[index])
|
||||
val compare = BinaryExpression(indexValue.copy(), ">=", NumericLiteral.optimalInteger(numlabels, ongoto.position), ongoto.position)
|
||||
|
@ -213,7 +213,7 @@ class TestNumericLiteral: FunSpec({
|
||||
test("cast can change value") {
|
||||
fun num(dt: BaseDataType, num: Double): NumericLiteral {
|
||||
val n = NumericLiteral(dt, num, Position.DUMMY)
|
||||
n.linkParents(AnonymousScope(mutableListOf(), Position.DUMMY))
|
||||
n.linkParents(AnonymousScope.empty())
|
||||
return n
|
||||
}
|
||||
val cast1 = num(BaseDataType.UBYTE, 200.0).cast(BaseDataType.BYTE, false)
|
||||
@ -233,7 +233,7 @@ class TestNumericLiteral: FunSpec({
|
||||
test("convert cannot change value") {
|
||||
fun num(dt: BaseDataType, num: Double): NumericLiteral {
|
||||
val n = NumericLiteral(dt, num, Position.DUMMY)
|
||||
n.linkParents(AnonymousScope(mutableListOf(), Position.DUMMY))
|
||||
n.linkParents(AnonymousScope.empty())
|
||||
return n
|
||||
}
|
||||
num(BaseDataType.UBYTE, 200.0).convertTypeKeepValue(BaseDataType.BYTE).isValid shouldBe false
|
||||
|
@ -22,7 +22,9 @@ import prog8.code.core.Position
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.vm.VmRunner
|
||||
import prog8tests.helpers.*
|
||||
import kotlin.io.path.readText
|
||||
|
||||
|
||||
class TestOptimization: FunSpec({
|
||||
@ -1178,4 +1180,54 @@ main {
|
||||
}"""
|
||||
compileText(C64Target(), false, src, outputDir, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("correct unused block removal for virtual target") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
cx16.r0++
|
||||
}
|
||||
}
|
||||
|
||||
some_block {
|
||||
uword buffer = memory("arena", 2000, 0)
|
||||
}
|
||||
|
||||
|
||||
other_block {
|
||||
sub redherring (uword buffer) {
|
||||
%ir {{
|
||||
loadm.w r99000,other_block.redherring.buffer
|
||||
}}
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = true)!!
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
||||
VmRunner().runProgram(virtfile.readText(), false)
|
||||
}
|
||||
|
||||
test("correct unused block removal for c64 target") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
cx16.r0++
|
||||
}
|
||||
}
|
||||
|
||||
some_block {
|
||||
uword buffer = memory("arena", 2000, 0)
|
||||
}
|
||||
|
||||
|
||||
other_block {
|
||||
sub redherring (uword buffer) {
|
||||
%asm {{
|
||||
lda #<p8b_other_block.p8s_redherring.p8v_buffer
|
||||
ldy #>p8b_other_block.p8s_redherring.p8v_buffer
|
||||
}}
|
||||
}
|
||||
}"""
|
||||
compileText(C64Target(), true, src, outputDir) shouldNotBe null
|
||||
}
|
||||
})
|
||||
|
@ -288,13 +288,13 @@ class TestProg8Parser: FunSpec( {
|
||||
}
|
||||
"""
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
assertPositionOf(module, Regex("^string:[0-9a-f\\-]+$"), 1, 0)
|
||||
assertPositionOf(module, Regex("^string:[0-9a-f\\-]+$"), 1, 1)
|
||||
}
|
||||
|
||||
test("of Module parsed from a file") {
|
||||
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val module = parseModule(ImportFileSystem.getFile(path))
|
||||
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
|
||||
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 1)
|
||||
}
|
||||
|
||||
test("of non-root Nodes parsed from file") {
|
||||
@ -302,7 +302,7 @@ class TestProg8Parser: FunSpec( {
|
||||
|
||||
val module = parseModule(ImportFileSystem.getFile(path))
|
||||
val mpf = module.position.file
|
||||
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
|
||||
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 1)
|
||||
val mainBlock = module.statements.filterIsInstance<Block>()[0]
|
||||
assertPositionOf(mainBlock, mpf, 2, 1, 4)
|
||||
val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0]
|
||||
|
@ -243,4 +243,40 @@ main {
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain ":8:5: address must be a constant"
|
||||
}
|
||||
|
||||
test("detect missing return") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
void read_loadlist()
|
||||
void test2()
|
||||
}
|
||||
|
||||
|
||||
sub read_loadlist() -> bool {
|
||||
cx16.r0++
|
||||
if cx16.r0==0
|
||||
return false
|
||||
cx16.r1++
|
||||
; TODO missing return! ERROR!
|
||||
}
|
||||
|
||||
sub test2() -> bool {
|
||||
cx16.r0++
|
||||
return false
|
||||
|
||||
sub sub1() {
|
||||
cx16.r0++
|
||||
}
|
||||
|
||||
sub sub2() {
|
||||
cx16.r0++
|
||||
}
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(Cx16Target(), false, src, outputDir, errors, false) shouldBe null
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "doesn't end with a return"
|
||||
}
|
||||
})
|
||||
|
@ -38,8 +38,7 @@ main {
|
||||
zz = words[3]
|
||||
}
|
||||
}"""
|
||||
val target = VMTarget()
|
||||
val result = compileText(target, false, src, outputDir, writeAssembly = true)!!
|
||||
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = true)!!
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
||||
VmRunner().runProgram(virtfile.readText(), false)
|
||||
}
|
||||
@ -58,8 +57,7 @@ main {
|
||||
zz = words[3]
|
||||
}
|
||||
}"""
|
||||
val target = VMTarget()
|
||||
val result = compileText(target, false, src, outputDir, writeAssembly = true)!!
|
||||
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = true)!!
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
||||
VmRunner().runProgram(virtfile.readText(), false)
|
||||
}
|
||||
@ -107,7 +105,7 @@ test {
|
||||
}
|
||||
}"""
|
||||
val target = VMTarget()
|
||||
var result = compileText(target, false, src, outputDir, writeAssembly = true)!!
|
||||
var result = compileText(VMTarget(), false, src, outputDir, writeAssembly = true)!!
|
||||
var virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
||||
VmRunner().runProgram(virtfile.readText(), false)
|
||||
|
||||
|
@ -1,842 +0,0 @@
|
||||
package prog8.ast.antlr
|
||||
|
||||
import org.antlr.v4.runtime.ParserRuleContext
|
||||
import org.antlr.v4.runtime.Token
|
||||
import org.antlr.v4.runtime.tree.TerminalNode
|
||||
import prog8.ast.FatalAstException
|
||||
import prog8.ast.SyntaxError
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.source.SourceCode
|
||||
import prog8.parser.Prog8ANTLRParser.*
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
|
||||
|
||||
/***************** Antlr Extension methods to create AST ****************/
|
||||
|
||||
private data class NumericLiteralNode(val number: Double, val datatype: BaseDataType)
|
||||
|
||||
|
||||
private fun ParserRuleContext.toPosition() : Position {
|
||||
val pathString = start.inputStream.sourceName
|
||||
val filename = if(SourceCode.isRegularFilesystemPath(pathString)) {
|
||||
val path = Path(pathString)
|
||||
if(path.isRegularFile()) {
|
||||
SourceCode.relative(path).toString()
|
||||
} else {
|
||||
path.toString()
|
||||
}
|
||||
} else {
|
||||
pathString
|
||||
}
|
||||
// note: beware of TAB characters in the source text, they count as 1 column...
|
||||
return Position(filename, start.line, start.charPositionInLine+1, start.charPositionInLine + 1 + start.stopIndex - start.startIndex)
|
||||
}
|
||||
|
||||
internal fun BlockContext.toAst(isInLibrary: Boolean) : Block {
|
||||
val blockstatements = block_statement().map {
|
||||
when {
|
||||
it.variabledeclaration()!=null -> it.variabledeclaration().toAst()
|
||||
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst()
|
||||
it.directive()!=null -> it.directive().toAst()
|
||||
it.inlineasm()!=null -> it.inlineasm().toAst()
|
||||
it.inlineir()!=null -> it.inlineir().toAst()
|
||||
it.labeldef()!=null -> it.labeldef().toAst()
|
||||
it.alias()!=null -> it.alias().toAst()
|
||||
else -> throw FatalAstException("weird block node $it")
|
||||
}
|
||||
}
|
||||
return Block(identifier().text, integerliteral()?.toAst()?.number?.toUInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
|
||||
}
|
||||
|
||||
private fun Statement_blockContext.toAst(): MutableList<Statement> =
|
||||
statement().asSequence().map { it.toAst() }.toMutableList()
|
||||
|
||||
private fun VariabledeclarationContext.toAst() : Statement {
|
||||
vardecl()?.let {
|
||||
return it.toAst(VarDeclType.VAR, null)
|
||||
}
|
||||
|
||||
varinitializer()?.let {
|
||||
return it.vardecl().toAst(VarDeclType.VAR, it.expression().toAst())
|
||||
}
|
||||
|
||||
constdecl()?.let {
|
||||
val cvarinit = it.varinitializer()
|
||||
return cvarinit.vardecl().toAst(VarDeclType.CONST, cvarinit.expression().toAst())
|
||||
}
|
||||
|
||||
memoryvardecl()?.let {
|
||||
val mvarinit = it.varinitializer()
|
||||
return mvarinit.vardecl().toAst(VarDeclType.MEMORY, mvarinit.expression().toAst())
|
||||
}
|
||||
|
||||
throw FatalAstException("weird variable decl $this")
|
||||
}
|
||||
|
||||
private fun SubroutinedeclarationContext.toAst() : Subroutine {
|
||||
return when {
|
||||
subroutine()!=null -> subroutine().toAst()
|
||||
asmsubroutine()!=null -> asmsubroutine().toAst()
|
||||
extsubroutine()!=null -> extsubroutine().toAst()
|
||||
else -> throw FatalAstException("weird subroutine decl $this")
|
||||
}
|
||||
}
|
||||
|
||||
private fun StatementContext.toAst() : Statement {
|
||||
val vardecl = variabledeclaration()?.toAst()
|
||||
if(vardecl!=null) return vardecl
|
||||
|
||||
val assignment = assignment()?.toAst()
|
||||
if(assignment!=null) return assignment
|
||||
|
||||
val augassign = augassignment()?.toAst()
|
||||
if(augassign!=null) return augassign
|
||||
|
||||
postincrdecr()?.let {
|
||||
val tgt = it.assign_target().toAst()
|
||||
val operator = it.operator.text
|
||||
val pos = it.toPosition()
|
||||
// print("\u001b[92mINFO\u001B[0m ") // bright green
|
||||
// println("${pos}: ++ and -- will be removed in a future version, please use +=1 or -=1 instead.") // .... if we decode to remove them one day
|
||||
val addSubOne = BinaryExpression(tgt.toExpression(), if(operator=="++") "+" else "-", NumericLiteral.optimalInteger(1, pos), pos)
|
||||
return Assignment(tgt, addSubOne, AssignmentOrigin.USERCODE, pos)
|
||||
}
|
||||
|
||||
val directive = directive()?.toAst()
|
||||
if(directive!=null) return directive
|
||||
|
||||
val label = labeldef()?.toAst()
|
||||
if(label!=null) return label
|
||||
|
||||
val jump = unconditionaljump()?.toAst()
|
||||
if(jump!=null) return jump
|
||||
|
||||
val fcall = functioncall_stmt()?.toAst()
|
||||
if(fcall!=null) return fcall
|
||||
|
||||
val ifstmt = if_stmt()?.toAst()
|
||||
if(ifstmt!=null) return ifstmt
|
||||
|
||||
val returnstmt = returnstmt()?.toAst()
|
||||
if(returnstmt!=null) return returnstmt
|
||||
|
||||
val subroutine = subroutinedeclaration()?.toAst()
|
||||
if(subroutine!=null) return subroutine
|
||||
|
||||
val asm = inlineasm()?.toAst()
|
||||
if(asm!=null) return asm
|
||||
|
||||
val ir = inlineir()?.toAst()
|
||||
if(ir!=null) return ir
|
||||
|
||||
val branchstmt = branch_stmt()?.toAst()
|
||||
if(branchstmt!=null) return branchstmt
|
||||
|
||||
val forloop = forloop()?.toAst()
|
||||
if(forloop!=null) return forloop
|
||||
|
||||
val untilloop = untilloop()?.toAst()
|
||||
if(untilloop!=null) return untilloop
|
||||
|
||||
val whileloop = whileloop()?.toAst()
|
||||
if(whileloop!=null) return whileloop
|
||||
|
||||
val repeatloop = repeatloop()?.toAst()
|
||||
if(repeatloop!=null) return repeatloop
|
||||
|
||||
val whenstmt = whenstmt()?.toAst()
|
||||
if(whenstmt!=null) return whenstmt
|
||||
|
||||
val breakstmt = breakstmt()?.toAst()
|
||||
if(breakstmt!=null) return breakstmt
|
||||
|
||||
val continuestmt = continuestmt()?.toAst()
|
||||
if(continuestmt!=null) return continuestmt
|
||||
|
||||
val unrollstmt = unrollloop()?.toAst()
|
||||
if(unrollstmt!=null) return unrollstmt
|
||||
|
||||
val deferstmt = defer()?.toAst()
|
||||
if(deferstmt!=null) return deferstmt
|
||||
|
||||
val aliasstmt = alias()?.toAst()
|
||||
if(aliasstmt!=null) return aliasstmt
|
||||
|
||||
val ongotostmt = ongoto()?.toAst()
|
||||
if(ongotostmt!=null) return ongotostmt
|
||||
|
||||
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
|
||||
}
|
||||
|
||||
private fun AsmsubroutineContext.toAst(): Subroutine {
|
||||
val inline = this.INLINE()!=null
|
||||
val subdecl = asmsub_decl().toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf()
|
||||
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes.toMutableList(),
|
||||
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
|
||||
subdecl.asmClobbers, null, true, inline, statements = statements, position = toPosition()
|
||||
)
|
||||
}
|
||||
|
||||
private fun ExtsubroutineContext.toAst(): Subroutine {
|
||||
val subdecl = asmsub_decl().toAst()
|
||||
val constbank = constbank?.toAst()?.number?.toUInt()?.toUByte()
|
||||
val varbank = varbank?.toAst()
|
||||
val addr = address.toAst()
|
||||
val address = Subroutine.Address(constbank, varbank, addr)
|
||||
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes.toMutableList(),
|
||||
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
|
||||
subdecl.asmClobbers, address, true, inline = false, statements = mutableListOf(), position = toPosition()
|
||||
)
|
||||
}
|
||||
|
||||
private class AsmsubDecl(val name: String,
|
||||
val parameters: List<SubroutineParameter>,
|
||||
val returntypes: List<DataType>,
|
||||
val asmParameterRegisters: List<RegisterOrStatusflag>,
|
||||
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
|
||||
val asmClobbers: Set<CpuRegister>)
|
||||
|
||||
private fun Asmsub_declContext.toAst(): AsmsubDecl {
|
||||
val name = identifier().text
|
||||
val params = asmsub_params()?.toAst() ?: emptyList()
|
||||
val returns = asmsub_returns()?.toAst() ?: emptyList()
|
||||
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
|
||||
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.zp, it.registerOrPair, it.position) }
|
||||
val normalReturntypes = returns.map { it.type }
|
||||
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
|
||||
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
|
||||
return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers)
|
||||
}
|
||||
|
||||
private class AsmSubroutineParameter(name: String,
|
||||
type: DataType,
|
||||
registerOrPair: RegisterOrPair?,
|
||||
val statusflag: Statusflag?,
|
||||
position: Position) : SubroutineParameter(name, type, ZeropageWish.DONTCARE, registerOrPair, position)
|
||||
|
||||
private class AsmSubroutineReturn(val type: DataType,
|
||||
val registerOrPair: RegisterOrPair?,
|
||||
val statusflag: Statusflag?)
|
||||
|
||||
private fun Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
|
||||
= asmsub_return().map {
|
||||
val register = it.register.text
|
||||
var registerorpair: RegisterOrPair? = null
|
||||
var statusregister: Statusflag? = null
|
||||
if(register!=null) {
|
||||
when (register) {
|
||||
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(register)
|
||||
in Statusflag.names -> statusregister = Statusflag.valueOf(register)
|
||||
else -> throw SyntaxError("invalid register or status flag", toPosition())
|
||||
}
|
||||
}
|
||||
// asmsubs currently only return a base datatype
|
||||
val returnBaseDt = it.datatype().toAst()
|
||||
AsmSubroutineReturn(
|
||||
DataType.forDt(returnBaseDt),
|
||||
registerorpair,
|
||||
statusregister)
|
||||
}
|
||||
|
||||
private fun Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter> = asmsub_param().map {
|
||||
val vardecl = it.vardecl()
|
||||
val baseDt = vardecl.datatype()?.toAst() ?: BaseDataType.UNDEFINED
|
||||
var datatype = DataType.forDt(baseDt)
|
||||
if(vardecl.ARRAYSIG()!=null || vardecl.arrayindex()!=null)
|
||||
datatype = datatype.elementToArray()
|
||||
val (registerorpair, statusregister) = parseParamRegister(it.register, it.toPosition())
|
||||
val identifiers = vardecl.identifier()
|
||||
if(identifiers.size>1)
|
||||
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
|
||||
val identifiername = getname(identifiers)
|
||||
AsmSubroutineParameter(identifiername, datatype, registerorpair, statusregister, toPosition())
|
||||
}
|
||||
|
||||
private fun getname(identifiers: MutableList<IdentifierContext>): String = identifiers[0].children[0].text
|
||||
|
||||
private fun getname(identifier: IdentifierContext): String = identifier.children[0].text
|
||||
// return if (identifier == null) null
|
||||
// else identifier.children[0].text
|
||||
// //else (identifier.NAME() ?: identifier.UNDERSCORENAME() ?: identifier.ON() ?: identifier.CALL()).text
|
||||
//}
|
||||
|
||||
|
||||
private fun parseParamRegister(registerTok: Token?, pos: Position): Pair<RegisterOrPair?, Statusflag?> {
|
||||
if(registerTok==null)
|
||||
return Pair(null, null)
|
||||
val register = registerTok.text
|
||||
var registerorpair: RegisterOrPair? = null
|
||||
var statusregister: Statusflag? = null
|
||||
if(register!=null) {
|
||||
when (register) {
|
||||
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(register)
|
||||
in Statusflag.names -> statusregister = Statusflag.valueOf(register)
|
||||
else -> {
|
||||
throw SyntaxError("invalid register or status flag", Position(pos.file, registerTok.line, registerTok.charPositionInLine, registerTok.charPositionInLine+1))
|
||||
}
|
||||
}
|
||||
}
|
||||
return Pair(registerorpair, statusregister)
|
||||
}
|
||||
|
||||
private fun Functioncall_stmtContext.toAst(): Statement {
|
||||
val void = this.VOID() != null
|
||||
val location = scoped_identifier().toAst()
|
||||
return if(expression_list() == null)
|
||||
FunctionCallStatement(location, mutableListOf(), void, toPosition())
|
||||
else
|
||||
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
|
||||
}
|
||||
|
||||
private fun FunctioncallContext.toAst(): FunctionCallExpression {
|
||||
val location = scoped_identifier().toAst()
|
||||
return if(expression_list() == null)
|
||||
FunctionCallExpression(location, mutableListOf(), toPosition())
|
||||
else
|
||||
FunctionCallExpression(location, expression_list().toAst().toMutableList(), toPosition())
|
||||
}
|
||||
|
||||
private fun InlineasmContext.toAst(): InlineAssembly {
|
||||
val text = INLINEASMBLOCK().text
|
||||
return InlineAssembly(text.substring(2, text.length-2), false, toPosition())
|
||||
}
|
||||
|
||||
private fun InlineirContext.toAst(): InlineAssembly {
|
||||
val text = INLINEASMBLOCK().text
|
||||
return InlineAssembly(text.substring(2, text.length-2), true, toPosition())
|
||||
}
|
||||
|
||||
private fun ReturnstmtContext.toAst() : Return {
|
||||
val values = if(returnvalues()==null || returnvalues().expression().isEmpty()) arrayOf() else returnvalues().expression().map { it.toAst() }.toTypedArray()
|
||||
return Return(values, toPosition())
|
||||
}
|
||||
|
||||
private fun UnconditionaljumpContext.toAst(): Jump {
|
||||
return Jump(expression().toAst(), toPosition())
|
||||
}
|
||||
|
||||
private fun LabeldefContext.toAst(): Statement =
|
||||
Label(children[0].text, toPosition())
|
||||
|
||||
private fun AliasContext.toAst(): Statement =
|
||||
Alias(identifier().text, scoped_identifier().toAst(), toPosition())
|
||||
|
||||
private fun SubroutineContext.toAst() : Subroutine {
|
||||
// non-asm subroutine
|
||||
val returntypes = sub_return_part()?.datatype()?.map { it.toAst() } ?: emptyList()
|
||||
return Subroutine(
|
||||
identifier().text,
|
||||
sub_params()?.toAst()?.toMutableList() ?: mutableListOf(),
|
||||
returntypes.map { DataType.forDt(it) }.toMutableList(),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptySet(),
|
||||
asmAddress = null,
|
||||
isAsmSubroutine = false,
|
||||
inline = false,
|
||||
statements = statement_block()?.toAst() ?: mutableListOf(),
|
||||
position = toPosition()
|
||||
)
|
||||
}
|
||||
|
||||
private fun Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
||||
sub_param().map {
|
||||
val decl = it.vardecl()
|
||||
val tags = decl.TAG().map { t -> t.text }
|
||||
val validTags = arrayOf("@zp", "@requirezp", "@nozp", "@split", "@nosplit", "@shared")
|
||||
for(tag in tags) {
|
||||
if(tag !in validTags)
|
||||
throw SyntaxError("invalid parameter tag '$tag'", toPosition())
|
||||
}
|
||||
val zp = getZpOption(tags)
|
||||
val baseDt = decl.datatype()?.toAst() ?: BaseDataType.UNDEFINED
|
||||
var datatype = DataType.forDt(baseDt)
|
||||
if(decl.ARRAYSIG()!=null || decl.arrayindex()!=null)
|
||||
datatype = datatype.elementToArray()
|
||||
|
||||
val identifiers = decl.identifier()
|
||||
if(identifiers.size>1)
|
||||
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
|
||||
val identifiername = getname(identifiers)
|
||||
|
||||
val (registerorpair, statusregister) = parseParamRegister(it.register, it.toPosition())
|
||||
if(statusregister!=null) {
|
||||
throw SyntaxError("can't use status register as param for normal subroutines", Position(toPosition().file, it.register.line, it.register.charPositionInLine, it.register.charPositionInLine+1))
|
||||
}
|
||||
SubroutineParameter(identifiername, datatype, zp, registerorpair, it.toPosition())
|
||||
}
|
||||
|
||||
private fun getZpOption(tags: List<String>): ZeropageWish = when {
|
||||
"@requirezp" in tags -> ZeropageWish.REQUIRE_ZEROPAGE
|
||||
"@zp" in tags -> ZeropageWish.PREFER_ZEROPAGE
|
||||
"@nozp" in tags -> ZeropageWish.NOT_IN_ZEROPAGE
|
||||
else -> ZeropageWish.DONTCARE
|
||||
}
|
||||
|
||||
private fun getSplitOption(tags: List<String>): SplitWish {
|
||||
return when {
|
||||
"@nosplit" in tags -> SplitWish.NOSPLIT
|
||||
"@split" in tags -> SplitWish.SPLIT
|
||||
else -> SplitWish.DONTCARE
|
||||
}
|
||||
}
|
||||
|
||||
private fun Assign_targetContext.toAst() : AssignTarget {
|
||||
return when(this) {
|
||||
is IdentifierTargetContext -> {
|
||||
val identifier = scoped_identifier().toAst()
|
||||
AssignTarget(identifier, null, null, null, false, scoped_identifier().toPosition())
|
||||
}
|
||||
is MemoryTargetContext ->
|
||||
AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), directmemory().toPosition()), null, false, toPosition())
|
||||
is ArrayindexedTargetContext -> {
|
||||
val ax = arrayindexed()
|
||||
val arrayvar = ax.scoped_identifier().toAst()
|
||||
val index = ax.arrayindex().toAst()
|
||||
val arrayindexed = ArrayIndexedExpression(arrayvar, index, ax.toPosition())
|
||||
AssignTarget(null, arrayindexed, null, null, false, toPosition())
|
||||
}
|
||||
is VoidTargetContext -> {
|
||||
AssignTarget(null, null, null, null, true, this.toPosition())
|
||||
}
|
||||
else -> throw FatalAstException("weird assign target node $this")
|
||||
}
|
||||
}
|
||||
|
||||
private fun Multi_assign_targetContext.toAst() : AssignTarget {
|
||||
val targets = this.assign_target().map { it.toAst() }
|
||||
return AssignTarget(null, null, null, targets, false, toPosition())
|
||||
}
|
||||
|
||||
private fun ClobberContext.toAst() : Set<CpuRegister> {
|
||||
val names = this.NAME().map { it.text }
|
||||
try {
|
||||
return names.map { CpuRegister.valueOf(it) }.toSet()
|
||||
} catch(_: IllegalArgumentException) {
|
||||
throw SyntaxError("invalid cpu register", toPosition())
|
||||
}
|
||||
}
|
||||
|
||||
private fun AssignmentContext.toAst(): Statement {
|
||||
val multiAssign = multi_assign_target()
|
||||
if(multiAssign!=null) {
|
||||
return Assignment(multiAssign.toAst(), expression().toAst(), AssignmentOrigin.USERCODE, toPosition())
|
||||
}
|
||||
|
||||
val nestedAssign = assignment()
|
||||
return if(nestedAssign==null)
|
||||
Assignment(assign_target().toAst(), expression().toAst(), AssignmentOrigin.USERCODE, toPosition())
|
||||
else
|
||||
ChainedAssignment(assign_target().toAst(), nestedAssign.toAst(), toPosition())
|
||||
}
|
||||
|
||||
private fun AugassignmentContext.toAst(): Assignment {
|
||||
// replace A += X with A = A + X
|
||||
val target = assign_target().toAst()
|
||||
val oper = operator.text.substringBefore('=')
|
||||
val expression = BinaryExpression(target.toExpression(), oper, expression().toAst(), expression().toPosition())
|
||||
return Assignment(assign_target().toAst(), expression, AssignmentOrigin.USERCODE, toPosition())
|
||||
}
|
||||
|
||||
private fun DatatypeContext.toAst(): BaseDataType {
|
||||
return try {
|
||||
BaseDataType.valueOf(text.uppercase())
|
||||
} catch (_: IllegalArgumentException) {
|
||||
BaseDataType.UNDEFINED
|
||||
}
|
||||
}
|
||||
|
||||
private fun ArrayindexContext.toAst() : ArrayIndex =
|
||||
ArrayIndex(expression().toAst(), toPosition())
|
||||
|
||||
internal fun DirectiveContext.toAst() : Directive {
|
||||
if(directivenamelist() != null) {
|
||||
val identifiers = directivenamelist().scoped_identifier().map { DirectiveArg(it.text, null, it.toPosition()) }
|
||||
return Directive(directivename.text, identifiers, toPosition())
|
||||
}
|
||||
else
|
||||
return Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
|
||||
}
|
||||
|
||||
private fun DirectiveargContext.toAst() : DirectiveArg {
|
||||
val str = stringliteral()
|
||||
if(str!=null) {
|
||||
if (str.encoding?.text != null)
|
||||
throw SyntaxError("don't use a string encoding for directive arguments", toPosition())
|
||||
return DirectiveArg(str.text.substring(1, text.length-1), integerliteral()?.toAst()?.number?.toUInt(), toPosition())
|
||||
}
|
||||
|
||||
return DirectiveArg(identifier()?.text, integerliteral()?.toAst()?.number?.toUInt(), toPosition())
|
||||
}
|
||||
|
||||
private fun IntegerliteralContext.toAst(): NumericLiteralNode {
|
||||
fun makeLiteral(literalTextWithGrouping: String, radix: Int): NumericLiteralNode {
|
||||
val literalText = literalTextWithGrouping.replace("_", "")
|
||||
val integer: Int
|
||||
var datatype = BaseDataType.UBYTE
|
||||
when (radix) {
|
||||
10 -> {
|
||||
integer = try {
|
||||
literalText.toInt()
|
||||
} catch(x: NumberFormatException) {
|
||||
throw SyntaxError("invalid decimal literal ${x.message}", toPosition())
|
||||
}
|
||||
datatype = when(integer) {
|
||||
in 0..255 -> BaseDataType.UBYTE
|
||||
in -128..127 -> BaseDataType.BYTE
|
||||
in 0..65535 -> BaseDataType.UWORD
|
||||
in -32768..32767 -> BaseDataType.WORD
|
||||
in -2147483647..2147483647 -> BaseDataType.LONG
|
||||
else -> BaseDataType.FLOAT
|
||||
}
|
||||
}
|
||||
2 -> {
|
||||
if(literalText.length>16)
|
||||
datatype = BaseDataType.LONG
|
||||
else if(literalText.length>8)
|
||||
datatype = BaseDataType.UWORD
|
||||
try {
|
||||
integer = literalText.toInt(2)
|
||||
} catch(x: NumberFormatException) {
|
||||
throw SyntaxError("invalid binary literal ${x.message}", toPosition())
|
||||
}
|
||||
}
|
||||
16 -> {
|
||||
if(literalText.length>4)
|
||||
datatype = BaseDataType.LONG
|
||||
else if(literalText.length>2)
|
||||
datatype = BaseDataType.UWORD
|
||||
try {
|
||||
integer = literalText.toInt(16)
|
||||
} catch(x: NumberFormatException) {
|
||||
throw SyntaxError("invalid hexadecimal literal ${x.message}", toPosition())
|
||||
}
|
||||
}
|
||||
else -> throw FatalAstException("invalid radix")
|
||||
}
|
||||
return NumericLiteralNode(integer.toDouble(), datatype)
|
||||
}
|
||||
val terminal: TerminalNode = children[0] as TerminalNode
|
||||
val integerPart = this.intpart.text
|
||||
return when (terminal.symbol.type) {
|
||||
DEC_INTEGER -> makeLiteral(integerPart, 10)
|
||||
HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16)
|
||||
BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2)
|
||||
else -> throw FatalAstException(terminal.text)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ExpressionContext.toAst(insideParentheses: Boolean=false) : Expression {
|
||||
|
||||
val litval = literalvalue()
|
||||
if(litval!=null) {
|
||||
val booleanlit = litval.booleanliteral()?.toAst()
|
||||
return if(booleanlit!=null) {
|
||||
NumericLiteral.fromBoolean(booleanlit, litval.toPosition())
|
||||
}
|
||||
else {
|
||||
val intLit = litval.integerliteral()?.toAst()
|
||||
when {
|
||||
intLit!=null -> when(intLit.datatype) {
|
||||
BaseDataType.UBYTE -> NumericLiteral(BaseDataType.UBYTE, intLit.number, litval.toPosition())
|
||||
BaseDataType.BYTE -> NumericLiteral(BaseDataType.BYTE, intLit.number, litval.toPosition())
|
||||
BaseDataType.UWORD -> NumericLiteral(BaseDataType.UWORD, intLit.number, litval.toPosition())
|
||||
BaseDataType.WORD -> NumericLiteral(BaseDataType.WORD, intLit.number, litval.toPosition())
|
||||
BaseDataType.LONG -> NumericLiteral(BaseDataType.LONG, intLit.number, litval.toPosition())
|
||||
BaseDataType.FLOAT -> NumericLiteral(BaseDataType.FLOAT, intLit.number, litval.toPosition())
|
||||
else -> throw FatalAstException("invalid datatype for numeric literal")
|
||||
}
|
||||
litval.floatliteral()!=null -> NumericLiteral(BaseDataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
|
||||
litval.stringliteral()!=null -> litval.stringliteral().toAst()
|
||||
litval.charliteral()!=null -> litval.charliteral().toAst()
|
||||
litval.arrayliteral()!=null -> {
|
||||
val array = litval.arrayliteral().toAst()
|
||||
// the actual type of the arraysize can not yet be determined here
|
||||
// the ConstantFold takes care of that and converts the type if needed.
|
||||
ArrayLiteral(InferredTypes.InferredType.unknown(), array, position = litval.toPosition())
|
||||
}
|
||||
else -> throw FatalAstException("invalid parsed literal")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(arrayindexed()!=null) {
|
||||
val ax = arrayindexed()
|
||||
val identifier = ax.scoped_identifier().toAst()
|
||||
val index = ax.arrayindex().toAst()
|
||||
return ArrayIndexedExpression(identifier, index, ax.toPosition())
|
||||
}
|
||||
|
||||
if(scoped_identifier()!=null)
|
||||
return scoped_identifier().toAst()
|
||||
|
||||
if(bop!=null) {
|
||||
val operator = bop.text.trim().replace("\\s+".toRegex(), " ")
|
||||
return BinaryExpression(
|
||||
left.toAst(),
|
||||
operator,
|
||||
right.toAst(),
|
||||
toPosition(),
|
||||
insideParentheses = insideParentheses
|
||||
)
|
||||
}
|
||||
|
||||
if(prefix!=null)
|
||||
return PrefixExpression(prefix.text, expression(0).toAst(), toPosition())
|
||||
|
||||
val funcall = functioncall()?.toAst()
|
||||
if(funcall!=null) return funcall
|
||||
|
||||
if (rangefrom!=null && rangeto!=null) {
|
||||
val defaultstep = if(rto.text == "to") 1 else -1
|
||||
val step = rangestep?.toAst() ?: NumericLiteral.optimalInteger(defaultstep, toPosition())
|
||||
return RangeExpression(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
|
||||
}
|
||||
|
||||
if(childCount==3 && children[0].text=="(" && children[2].text==")")
|
||||
return expression(0).toAst(insideParentheses=true) // expression within ( )
|
||||
|
||||
if(typecast()!=null) {
|
||||
// typecast is always to a base datatype
|
||||
val baseDt = typecast().datatype().toAst()
|
||||
return TypecastExpression(expression(0).toAst(), baseDt, false, toPosition())
|
||||
}
|
||||
|
||||
if(directmemory()!=null)
|
||||
return DirectMemoryRead(directmemory().expression().toAst(), toPosition())
|
||||
|
||||
if(addressof()!=null) {
|
||||
val addressOf = addressof()
|
||||
val identifier = addressOf.scoped_identifier()
|
||||
val msb = addressOf.ADDRESS_OF_MSB()!=null
|
||||
// note: &< (ADDRESS_OF_LSB) is equivalent to a regular &.
|
||||
return if (identifier != null)
|
||||
AddressOf(addressof().scoped_identifier().toAst(), null, msb, toPosition())
|
||||
else {
|
||||
val array = addressOf.arrayindexed()
|
||||
AddressOf(array.scoped_identifier().toAst(), array.arrayindex().toAst(), msb, toPosition())
|
||||
}
|
||||
}
|
||||
|
||||
if(if_expression()!=null) {
|
||||
val ifex = if_expression()
|
||||
val (condition, truevalue, falsevalue) = ifex.expression()
|
||||
return IfExpression(condition.toAst(), truevalue.toAst(), falsevalue.toAst(), toPosition())
|
||||
}
|
||||
|
||||
if(sizeof_expression!=null) {
|
||||
val datatype = sizeof_argument().datatype()?.toAst()
|
||||
val expression = sizeof_argument().expression()?.toAst()
|
||||
val sizeof = IdentifierReference(listOf("sizeof"), toPosition())
|
||||
val arg = if(expression!=null) expression else {
|
||||
require(datatype!=null)
|
||||
IdentifierReference(listOf(datatype.name.lowercase()), toPosition())
|
||||
}
|
||||
return FunctionCallExpression(sizeof, mutableListOf(arg), toPosition())
|
||||
}
|
||||
|
||||
throw FatalAstException(text)
|
||||
}
|
||||
|
||||
private fun CharliteralContext.toAst(): CharLiteral {
|
||||
val text = this.SINGLECHAR().text
|
||||
val enc = this.encoding?.text
|
||||
val encoding =
|
||||
if(enc!=null)
|
||||
Encoding.entries.singleOrNull { it.prefix == enc }
|
||||
?: throw SyntaxError("invalid encoding", toPosition())
|
||||
else
|
||||
Encoding.DEFAULT
|
||||
val raw = text.substring(1, text.length - 1)
|
||||
try {
|
||||
return CharLiteral.fromEscaped(raw, encoding, toPosition())
|
||||
} catch(ex: IllegalArgumentException) {
|
||||
throw SyntaxError(ex.message!!, toPosition())
|
||||
}
|
||||
}
|
||||
|
||||
private fun StringliteralContext.toAst(): StringLiteral {
|
||||
val text=this.STRING().text
|
||||
val enc = encoding?.text
|
||||
val encoding =
|
||||
if(enc!=null)
|
||||
Encoding.entries.singleOrNull { it.prefix == enc }
|
||||
?: throw SyntaxError("invalid encoding", toPosition())
|
||||
else
|
||||
Encoding.DEFAULT
|
||||
val raw = text.substring(1, text.length-1)
|
||||
try {
|
||||
return StringLiteral.fromEscaped(raw, encoding, toPosition())
|
||||
} catch(ex: IllegalArgumentException) {
|
||||
throw SyntaxError(ex.message!!, toPosition())
|
||||
}
|
||||
}
|
||||
|
||||
private fun Expression_listContext.toAst() = expression().map{ it.toAst() }
|
||||
|
||||
private fun Scoped_identifierContext.toAst() : IdentifierReference {
|
||||
return IdentifierReference(identifier().map { it.text }, toPosition())
|
||||
}
|
||||
|
||||
private fun FloatliteralContext.toAst() = text.replace("_","").toDouble()
|
||||
|
||||
private fun BooleanliteralContext.toAst() = when(text) {
|
||||
"true" -> true
|
||||
"false" -> false
|
||||
else -> throw FatalAstException(text)
|
||||
}
|
||||
|
||||
private fun ArrayliteralContext.toAst() : Array<Expression> =
|
||||
expression().map { it.toAst() }.toTypedArray()
|
||||
|
||||
private fun If_stmtContext.toAst(): IfElse {
|
||||
val condition = expression().toAst()
|
||||
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val elseStatements = else_part()?.toAst() ?: mutableListOf()
|
||||
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
|
||||
?: statement().toPosition())
|
||||
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
|
||||
return IfElse(condition, trueScope, elseScope, toPosition())
|
||||
}
|
||||
|
||||
private fun Else_partContext.toAst(): MutableList<Statement> {
|
||||
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
}
|
||||
|
||||
private fun Branch_stmtContext.toAst(): ConditionalBranch {
|
||||
val branchcondition = branchcondition().toAst()
|
||||
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val elseStatements = else_part()?.toAst() ?: mutableListOf()
|
||||
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
|
||||
?: statement().toPosition())
|
||||
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
|
||||
return ConditionalBranch(branchcondition, trueScope, elseScope, toPosition())
|
||||
}
|
||||
|
||||
private fun BranchconditionContext.toAst() = BranchCondition.valueOf(
|
||||
text.substringAfter('_').uppercase()
|
||||
)
|
||||
|
||||
private fun ForloopContext.toAst(): ForLoop {
|
||||
val loopvar = scoped_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(loopvar, iterable, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun BreakstmtContext.toAst() = Break(toPosition())
|
||||
|
||||
private fun ContinuestmtContext.toAst() = Continue(toPosition())
|
||||
|
||||
private fun DeferContext.toAst(): Defer {
|
||||
val block = statement_block()?.toAst()
|
||||
if(block!=null) {
|
||||
val scope = AnonymousScope(block, statement_block()?.toPosition() ?: toPosition())
|
||||
return Defer(scope, toPosition())
|
||||
}
|
||||
val singleStmt = statement()!!.toAst()
|
||||
val scope = AnonymousScope(mutableListOf(singleStmt), statement().toPosition())
|
||||
return Defer(scope, toPosition())
|
||||
}
|
||||
|
||||
private fun WhileloopContext.toAst(): WhileLoop {
|
||||
val condition = expression().toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||
?: statement().toPosition())
|
||||
return WhileLoop(condition, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun 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 RepeatLoop(iterations, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun UnrollloopContext.toAst(): UnrollLoop {
|
||||
val iterations = expression().toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||
?: statement().toPosition())
|
||||
return UnrollLoop(iterations, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun 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 UntilLoop(scope, untilCondition, toPosition())
|
||||
}
|
||||
|
||||
private fun WhenstmtContext.toAst(): When {
|
||||
val condition = expression().toAst()
|
||||
val choices = this.when_choice()?.map { it.toAst() }?.toMutableList() ?: mutableListOf()
|
||||
return When(condition, choices, toPosition())
|
||||
}
|
||||
|
||||
private fun When_choiceContext.toAst(): WhenChoice {
|
||||
val values = expression_list()?.toAst()
|
||||
val stmt = statement()?.toAst()
|
||||
val stmtBlock = statement_block()?.toAst()?.toMutableList() ?: mutableListOf()
|
||||
if(stmt!=null)
|
||||
stmtBlock.add(stmt)
|
||||
val scope = AnonymousScope(stmtBlock, toPosition())
|
||||
return WhenChoice(values?.toMutableList(), scope, toPosition())
|
||||
}
|
||||
|
||||
private fun VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl {
|
||||
val tags = TAG().map { it.text }
|
||||
val validTags = arrayOf("@zp", "@requirezp", "@nozp", "@split", "@nosplit", "@shared", "@alignword", "@alignpage", "@align64", "@dirty")
|
||||
for(tag in tags) {
|
||||
if(tag !in validTags)
|
||||
throw SyntaxError("invalid variable tag '$tag'", toPosition())
|
||||
}
|
||||
val zp = getZpOption(tags)
|
||||
val split = getSplitOption(tags)
|
||||
val identifiers = identifier()
|
||||
val identifiername = getname(identifiers)
|
||||
val name = if(identifiers.size==1) identifiername else "<multiple>"
|
||||
val isArray = ARRAYSIG() != null || arrayindex() != null
|
||||
val alignword = "@alignword" in tags
|
||||
val align64 = "@align64" in tags
|
||||
val alignpage = "@alignpage" in tags
|
||||
if(alignpage && alignword)
|
||||
throw SyntaxError("choose a single alignment option", toPosition())
|
||||
val baseDt = datatype()?.toAst() ?: BaseDataType.UNDEFINED
|
||||
val dt = if(isArray) DataType.arrayFor(baseDt, split!=SplitWish.NOSPLIT) else DataType.forDt(baseDt)
|
||||
|
||||
return VarDecl(
|
||||
type, VarDeclOrigin.USERCODE,
|
||||
dt,
|
||||
zp,
|
||||
split,
|
||||
arrayindex()?.toAst(),
|
||||
name,
|
||||
if(identifiers.size==1) emptyList() else identifiers.map { getname(it) },
|
||||
value,
|
||||
"@shared" in tags,
|
||||
if(alignword) 2u else if(align64) 64u else if(alignpage) 256u else 0u,
|
||||
"@dirty" in tags,
|
||||
toPosition()
|
||||
)
|
||||
}
|
||||
|
||||
private fun OngotoContext.toAst(): Statement {
|
||||
val elseStatements = this.else_part()?.toAst()
|
||||
val elseScope = if(elseStatements==null) null else AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
|
||||
val isCall = this.kind.text == "call"
|
||||
val index = this.expression().toAst()
|
||||
val labels = directivenamelist().scoped_identifier().map { it.toAst() }
|
||||
return OnGoto(isCall, index, labels, elseScope, toPosition())
|
||||
}
|
750
compilerAst/src/prog8/ast/antlr/Antlr2KotlinVisitor.kt
Normal file
750
compilerAst/src/prog8/ast/antlr/Antlr2KotlinVisitor.kt
Normal file
@ -0,0 +1,750 @@
|
||||
package prog8.ast.antlr
|
||||
|
||||
import org.antlr.v4.runtime.ParserRuleContext
|
||||
import org.antlr.v4.runtime.Token
|
||||
import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor
|
||||
import org.antlr.v4.runtime.tree.TerminalNode
|
||||
import prog8.ast.FatalAstException
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.SyntaxError
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.source.SourceCode
|
||||
import prog8.parser.Prog8ANTLRParser.*
|
||||
import prog8.parser.Prog8ANTLRVisitor
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
|
||||
|
||||
class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node>(), Prog8ANTLRVisitor<Node> {
|
||||
|
||||
override fun visitModule(ctx: ModuleContext): Module {
|
||||
val statements = ctx.module_element().map { it.accept(this) as Statement }
|
||||
return Module(statements.toMutableList(), ctx.toPosition(), source)
|
||||
}
|
||||
|
||||
override fun visitBlock(ctx: BlockContext): Block {
|
||||
val name = getname(ctx.identifier())
|
||||
val address = (ctx.integerliteral()?.accept(this) as NumericLiteral?)?.number?.toUInt()
|
||||
val statements = ctx.block_statement().map { it.accept(this) as Statement }
|
||||
return Block(name, address, statements.toMutableList(), source.isFromLibrary, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitExpression(ctx: ExpressionContext): Expression {
|
||||
if(ctx.sizeof_expression!=null) {
|
||||
val sdt = ctx.sizeof_argument().datatype()
|
||||
val datatype = if(sdt!=null) baseDatatypeFor(sdt) else null
|
||||
val expression = ctx.sizeof_argument().expression()?.accept(this) as Expression?
|
||||
val sizeof = IdentifierReference(listOf("sizeof"), ctx.toPosition())
|
||||
val arg = if (expression != null) expression else {
|
||||
require(datatype != null)
|
||||
IdentifierReference(listOf(datatype.name.lowercase()), ctx.toPosition())
|
||||
}
|
||||
return FunctionCallExpression(sizeof, mutableListOf(arg), ctx.toPosition())
|
||||
}
|
||||
|
||||
if(ctx.bop!=null) {
|
||||
val operator = ctx.bop.text.trim().replace("\\s+".toRegex(), " ")
|
||||
return BinaryExpression(
|
||||
ctx.left.accept(this) as Expression,
|
||||
operator,
|
||||
ctx.right.accept(this) as Expression,
|
||||
ctx.toPosition()
|
||||
)
|
||||
}
|
||||
|
||||
if(ctx.prefix!=null) {
|
||||
return PrefixExpression(ctx.prefix.text, ctx.expression(0).accept(this) as Expression, ctx.toPosition())
|
||||
}
|
||||
|
||||
if(ctx.rangefrom!=null && ctx.rangeto!=null) {
|
||||
val defaultstep = if(ctx.rto.text == "to") 1 else -1
|
||||
return RangeExpression(
|
||||
ctx.rangefrom.accept(this) as Expression,
|
||||
ctx.rangeto.accept(this) as Expression,
|
||||
ctx.rangestep?.accept(this) as Expression? ?: NumericLiteral.optimalInteger(defaultstep, ctx.toPosition()),
|
||||
ctx.toPosition())
|
||||
}
|
||||
|
||||
if(ctx.typecast()!=null) {
|
||||
// typecast is always to a base datatype
|
||||
val baseDt = baseDatatypeFor(ctx.typecast().datatype())
|
||||
return TypecastExpression(ctx.expression(0).accept(this) as Expression, baseDt, false, ctx.toPosition())
|
||||
}
|
||||
|
||||
if(ctx.childCount==3 && ctx.children[0].text=="(" && ctx.children[2].text==")")
|
||||
return ctx.expression(0).accept(this) as Expression // expression within ( )
|
||||
|
||||
return visitChildren(ctx) as Expression
|
||||
}
|
||||
|
||||
override fun visitSubroutinedeclaration(ctx: SubroutinedeclarationContext): Subroutine {
|
||||
if(ctx.subroutine()!=null)
|
||||
return ctx.subroutine().accept(this) as Subroutine
|
||||
if(ctx.asmsubroutine()!=null)
|
||||
return ctx.asmsubroutine().accept(this) as Subroutine
|
||||
if(ctx.extsubroutine()!=null)
|
||||
return ctx.extsubroutine().accept(this) as Subroutine
|
||||
throw FatalAstException("weird subroutine")
|
||||
}
|
||||
|
||||
override fun visitAlias(ctx: AliasContext): Alias {
|
||||
val identifier = getname(ctx.identifier())
|
||||
val target = ctx.scoped_identifier().accept(this) as IdentifierReference
|
||||
return Alias(identifier, target, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitDefer(ctx: DeferContext): Defer {
|
||||
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
|
||||
return Defer(statements, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitLabeldef(ctx: LabeldefContext): Label {
|
||||
return Label(getname(ctx.identifier()), ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitUnconditionaljump(ctx: UnconditionaljumpContext): Jump {
|
||||
return Jump(ctx.expression().accept(this) as Expression, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitDirective(ctx: DirectiveContext): Directive {
|
||||
if(ctx.directivenamelist() != null) {
|
||||
val namelist = ctx.directivenamelist().scoped_identifier().map { it.accept(this) as IdentifierReference }
|
||||
val identifiers = namelist.map { DirectiveArg(it.nameInSource.joinToString("."), null, ctx.toPosition()) }
|
||||
return Directive(ctx.directivename.text, identifiers, ctx.toPosition())
|
||||
}
|
||||
else
|
||||
return Directive(ctx.directivename.text, ctx.directivearg().map { it.accept(this) as DirectiveArg }, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitDirectivearg(ctx: DirectiveargContext): DirectiveArg {
|
||||
val integer = (ctx.integerliteral()?.accept(this) as NumericLiteral?)?.number?.toUInt()
|
||||
val str = ctx.stringliteral()
|
||||
if(str!=null) {
|
||||
if (str.encoding?.text != null)
|
||||
throw SyntaxError("don't use a string encoding for directive arguments", ctx.toPosition())
|
||||
return DirectiveArg(str.text.substring(1, str.text.length-1), integer, ctx.toPosition())
|
||||
}
|
||||
val identifier = ctx.identifier()?.accept(this) as IdentifierReference?
|
||||
return DirectiveArg(identifier?.nameInSource?.single(), integer, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitVardecl(ctx: VardeclContext): VarDecl {
|
||||
val tags = ctx.TAG().map { it.text }
|
||||
val validTags = arrayOf("@zp", "@requirezp", "@nozp", "@split", "@nosplit", "@shared", "@alignword", "@alignpage", "@align64", "@dirty")
|
||||
for(tag in tags) {
|
||||
if(tag !in validTags)
|
||||
throw SyntaxError("invalid variable tag '$tag'", ctx.toPosition())
|
||||
}
|
||||
val zp = getZpOption(tags)
|
||||
val split = getSplitOption(tags)
|
||||
val alignword = "@alignword" in tags
|
||||
val align64 = "@align64" in tags
|
||||
val alignpage = "@alignpage" in tags
|
||||
if(alignpage && alignword)
|
||||
throw SyntaxError("choose a single alignment option", ctx.toPosition())
|
||||
|
||||
val identifiers = ctx.identifier().map { getname(it) }
|
||||
val identifiername = identifiers[0]
|
||||
val name = if(identifiers.size==1) identifiername else "<multiple>"
|
||||
|
||||
val arrayIndex = ctx.arrayindex()?.accept(this) as ArrayIndex?
|
||||
val isArray = ctx.ARRAYSIG() != null || arrayIndex != null
|
||||
val baseDt = baseDatatypeFor(ctx.datatype())
|
||||
val dt = if(isArray) DataType.arrayFor(baseDt, split!=SplitWish.NOSPLIT) else DataType.forDt(baseDt)
|
||||
|
||||
return VarDecl(
|
||||
VarDeclType.VAR, // can be changed to MEMORY or CONST as required
|
||||
VarDeclOrigin.USERCODE,
|
||||
dt,
|
||||
zp,
|
||||
split,
|
||||
arrayIndex,
|
||||
name,
|
||||
if(identifiers.size==1) emptyList() else identifiers,
|
||||
null,
|
||||
"@shared" in tags,
|
||||
if(alignword) 2u else if(align64) 64u else if(alignpage) 256u else 0u,
|
||||
"@dirty" in tags,
|
||||
ctx.toPosition()
|
||||
)
|
||||
}
|
||||
|
||||
override fun visitVarinitializer(ctx: VarinitializerContext): VarDecl {
|
||||
val vardecl = ctx.vardecl().accept(this) as VarDecl
|
||||
vardecl.value = ctx.expression().accept(this) as Expression
|
||||
return vardecl
|
||||
}
|
||||
|
||||
override fun visitConstdecl(ctx: ConstdeclContext): VarDecl {
|
||||
val vardecl = ctx.varinitializer().accept(this) as VarDecl
|
||||
vardecl.type = VarDeclType.CONST
|
||||
return vardecl
|
||||
}
|
||||
|
||||
override fun visitMemoryvardecl(ctx: MemoryvardeclContext): VarDecl {
|
||||
val vardecl = ctx.varinitializer().accept(this) as VarDecl
|
||||
vardecl.type = VarDeclType.MEMORY
|
||||
return vardecl
|
||||
}
|
||||
|
||||
override fun visitArrayindex(ctx: ArrayindexContext): ArrayIndex {
|
||||
return ArrayIndex(ctx.expression().accept(this) as Expression, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitAssignment(ctx: AssignmentContext): Statement {
|
||||
val multiAssign = ctx.multi_assign_target()
|
||||
if(multiAssign!=null) {
|
||||
return Assignment(multiAssign.accept(this) as AssignTarget, ctx.expression().accept(this) as Expression, AssignmentOrigin.USERCODE, ctx.toPosition())
|
||||
}
|
||||
|
||||
val nestedAssign = ctx.assignment()
|
||||
return if(nestedAssign==null)
|
||||
Assignment(ctx.assign_target().accept(this) as AssignTarget, ctx.expression().accept(this) as Expression, AssignmentOrigin.USERCODE, ctx.toPosition())
|
||||
else
|
||||
ChainedAssignment(ctx.assign_target().accept(this) as AssignTarget, nestedAssign.accept(this) as Statement, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitAugassignment(ctx: AugassignmentContext): Assignment {
|
||||
// replace A += X with A = A + X
|
||||
val target = ctx.assign_target().accept(this) as AssignTarget
|
||||
val oper = ctx.operator.text.substringBefore('=')
|
||||
val expression = BinaryExpression(target.toExpression(), oper, ctx.expression().accept(this) as Expression, ctx.toPosition())
|
||||
return Assignment(target, expression, AssignmentOrigin.USERCODE, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitIdentifierTarget(ctx: IdentifierTargetContext): AssignTarget {
|
||||
val identifier = ctx.scoped_identifier().accept(this) as IdentifierReference
|
||||
return AssignTarget(identifier, null, null, null, false, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitArrayindexedTarget(ctx: ArrayindexedTargetContext): AssignTarget {
|
||||
val ax = ctx.arrayindexed()
|
||||
val arrayvar = ax.scoped_identifier().accept(this) as IdentifierReference
|
||||
val index = ax.arrayindex().accept(this) as ArrayIndex
|
||||
val arrayindexed = ArrayIndexedExpression(arrayvar, index, ax.toPosition())
|
||||
return AssignTarget(null, arrayindexed, null, null, false, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitMemoryTarget(ctx: MemoryTargetContext): AssignTarget {
|
||||
return AssignTarget(null, null,
|
||||
DirectMemoryWrite(ctx.directmemory().expression().accept(this) as Expression, ctx.toPosition()),
|
||||
null, false, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitVoidTarget(ctx: VoidTargetContext): AssignTarget {
|
||||
return AssignTarget(null, null, null, null, true, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitMulti_assign_target(ctx: Multi_assign_targetContext): AssignTarget {
|
||||
val targets = ctx.assign_target().map { it.accept(this) as AssignTarget }
|
||||
return AssignTarget(null, null, null, targets, false, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitPostincrdecr(ctx: PostincrdecrContext): Assignment {
|
||||
val tgt = ctx.assign_target().accept(this) as AssignTarget
|
||||
val operator = ctx.operator.text
|
||||
val pos = ctx.toPosition()
|
||||
val addSubOne = BinaryExpression(tgt.toExpression(), if(operator=="++") "+" else "-", NumericLiteral.optimalInteger(1, pos), pos)
|
||||
return Assignment(tgt, addSubOne, AssignmentOrigin.USERCODE, pos)
|
||||
}
|
||||
|
||||
override fun visitArrayindexed(ctx: ArrayindexedContext): ArrayIndexedExpression {
|
||||
val identifier = ctx.scoped_identifier().accept(this) as IdentifierReference
|
||||
val index = ctx.arrayindex().accept(this) as ArrayIndex
|
||||
return ArrayIndexedExpression(identifier, index, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitDirectmemory(ctx: DirectmemoryContext): DirectMemoryRead {
|
||||
return DirectMemoryRead(ctx.expression().accept(this) as Expression, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitAddressof(ctx: AddressofContext): AddressOf {
|
||||
val identifier = ctx.scoped_identifier()?.accept(this) as IdentifierReference?
|
||||
val msb = ctx.ADDRESS_OF_MSB()!=null
|
||||
// note: &< (ADDRESS_OF_LSB) is equivalent to a regular &.
|
||||
return if (identifier != null)
|
||||
AddressOf(identifier, null, msb, ctx.toPosition())
|
||||
else {
|
||||
val array = ctx.arrayindexed()
|
||||
AddressOf(array.scoped_identifier().accept(this) as IdentifierReference,
|
||||
array.arrayindex().accept(this) as ArrayIndex,
|
||||
msb, ctx.toPosition())
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitFunctioncall(ctx: FunctioncallContext): FunctionCallExpression {
|
||||
val name = ctx.scoped_identifier().accept(this) as IdentifierReference
|
||||
val args = ctx.expression_list()?.expression()?.map { it.accept(this) as Expression } ?: emptyList()
|
||||
return FunctionCallExpression(name, args.toMutableList(), ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitFunctioncall_stmt(ctx: Functioncall_stmtContext): FunctionCallStatement {
|
||||
val void = ctx.VOID() != null
|
||||
val name = ctx.scoped_identifier().accept(this) as IdentifierReference
|
||||
val args = ctx.expression_list()?.expression()?.map { it.accept(this) as Expression } ?: emptyList()
|
||||
return FunctionCallStatement(name, args.toMutableList(), void, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitReturnstmt(ctx: ReturnstmtContext): Return {
|
||||
val cvalues = ctx.returnvalues()
|
||||
val values = if(cvalues==null || cvalues.expression().isEmpty()) arrayOf() else cvalues.expression().map { it.accept(this) as Expression }.toTypedArray()
|
||||
return Return(values, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitBreakstmt(ctx: BreakstmtContext): Break {
|
||||
return Break(ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitContinuestmt(ctx: ContinuestmtContext): Continue {
|
||||
return Continue(ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitIdentifier(ctx: IdentifierContext): IdentifierReference {
|
||||
return IdentifierReference(listOf(getname(ctx)), ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitScoped_identifier(ctx: Scoped_identifierContext): IdentifierReference {
|
||||
val children = ctx.identifier().map { it.text }
|
||||
return IdentifierReference(children, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitIntegerliteral(ctx: IntegerliteralContext): NumericLiteral {
|
||||
|
||||
fun makeLiteral(literalTextWithGrouping: String, radix: Int): Pair<Double, BaseDataType> {
|
||||
val literalText = literalTextWithGrouping.replace("_", "")
|
||||
val integer: Int
|
||||
var datatype = BaseDataType.UBYTE
|
||||
when (radix) {
|
||||
10 -> {
|
||||
integer = try {
|
||||
literalText.toInt()
|
||||
} catch(x: NumberFormatException) {
|
||||
throw SyntaxError("invalid decimal literal ${x.message}", ctx.toPosition())
|
||||
}
|
||||
datatype = when(integer) {
|
||||
in 0..255 -> BaseDataType.UBYTE
|
||||
in -128..127 -> BaseDataType.BYTE
|
||||
in 0..65535 -> BaseDataType.UWORD
|
||||
in -32768..32767 -> BaseDataType.WORD
|
||||
in -2147483647..2147483647 -> BaseDataType.LONG
|
||||
else -> BaseDataType.FLOAT
|
||||
}
|
||||
}
|
||||
2 -> {
|
||||
if(literalText.length>16)
|
||||
datatype = BaseDataType.LONG
|
||||
else if(literalText.length>8)
|
||||
datatype = BaseDataType.UWORD
|
||||
try {
|
||||
integer = literalText.toInt(2)
|
||||
} catch(x: NumberFormatException) {
|
||||
throw SyntaxError("invalid binary literal ${x.message}", ctx.toPosition())
|
||||
}
|
||||
}
|
||||
16 -> {
|
||||
if(literalText.length>4)
|
||||
datatype = BaseDataType.LONG
|
||||
else if(literalText.length>2)
|
||||
datatype = BaseDataType.UWORD
|
||||
try {
|
||||
integer = literalText.toInt(16)
|
||||
} catch(x: NumberFormatException) {
|
||||
throw SyntaxError("invalid hexadecimal literal ${x.message}", ctx.toPosition())
|
||||
}
|
||||
}
|
||||
else -> throw FatalAstException("invalid radix")
|
||||
}
|
||||
return integer.toDouble() to datatype
|
||||
}
|
||||
|
||||
val terminal: TerminalNode = ctx.children[0] as TerminalNode
|
||||
val integerPart = ctx.intpart.text
|
||||
val integer = when (terminal.symbol.type) {
|
||||
DEC_INTEGER -> makeLiteral(integerPart, 10)
|
||||
HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16)
|
||||
BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2)
|
||||
else -> throw FatalAstException(terminal.text)
|
||||
}
|
||||
return NumericLiteral(integer.second, integer.first, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitBooleanliteral(ctx: BooleanliteralContext): NumericLiteral {
|
||||
val boolean = when(ctx.text) {
|
||||
"true" -> true
|
||||
"false" -> false
|
||||
else -> throw FatalAstException(ctx.text)
|
||||
}
|
||||
return NumericLiteral.fromBoolean(boolean, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitArrayliteral(ctx: ArrayliteralContext): ArrayLiteral {
|
||||
val array = ctx.expression().map { it.accept(this) as Expression }.toTypedArray()
|
||||
// the actual type of the arraysize can not yet be determined here
|
||||
// the ConstantFold takes care of that and converts the type if needed.
|
||||
return ArrayLiteral(InferredTypes.InferredType.unknown(), array, position = ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitStringliteral(ctx: StringliteralContext): StringLiteral {
|
||||
val text = ctx.STRING().text
|
||||
val enc = ctx.encoding?.text
|
||||
val encoding =
|
||||
if(enc!=null)
|
||||
Encoding.entries.singleOrNull { it.prefix == enc }
|
||||
?: throw SyntaxError("invalid encoding", ctx.toPosition())
|
||||
else
|
||||
Encoding.DEFAULT
|
||||
val raw = text.substring(1, text.length-1)
|
||||
try {
|
||||
return StringLiteral.fromEscaped(raw, encoding, ctx.toPosition())
|
||||
} catch(ex: IllegalArgumentException) {
|
||||
throw SyntaxError(ex.message!!, ctx.toPosition())
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitCharliteral(ctx: CharliteralContext): CharLiteral {
|
||||
val text = ctx.SINGLECHAR().text
|
||||
val enc = ctx.encoding?.text
|
||||
val encoding =
|
||||
if(enc!=null)
|
||||
Encoding.entries.singleOrNull { it.prefix == enc }
|
||||
?: throw SyntaxError("invalid encoding", ctx.toPosition())
|
||||
else
|
||||
Encoding.DEFAULT
|
||||
val raw = text.substring(1, text.length - 1)
|
||||
try {
|
||||
return CharLiteral.fromEscaped(raw, encoding, ctx.toPosition())
|
||||
} catch(ex: IllegalArgumentException) {
|
||||
throw SyntaxError(ex.message!!, ctx.toPosition())
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitFloatliteral(ctx: FloatliteralContext): NumericLiteral {
|
||||
val floatvalue = ctx.text.replace("_","").toDouble()
|
||||
return NumericLiteral(BaseDataType.FLOAT, floatvalue, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitInlineasm(ctx: InlineasmContext): InlineAssembly {
|
||||
val text = ctx.INLINEASMBLOCK().text
|
||||
return InlineAssembly(text.substring(2, text.length-2), false, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitInlineir(ctx: InlineirContext): InlineAssembly {
|
||||
val text = ctx.INLINEASMBLOCK().text
|
||||
return InlineAssembly(text.substring(2, text.length-2), true, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitSubroutine(ctx: SubroutineContext): Subroutine {
|
||||
val name = getname(ctx.identifier())
|
||||
val parameters = ctx.sub_params()?.sub_param()?.map { it.accept(this) as SubroutineParameter } ?: emptyList()
|
||||
val returntypes = ctx.sub_return_part()?.datatype()?. map { dataTypeFor(it) } ?: emptyList()
|
||||
val statements = ctx.statement_block().accept(this) as AnonymousScope
|
||||
return Subroutine(
|
||||
name,
|
||||
parameters.toMutableList(),
|
||||
returntypes.toMutableList(),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptySet(),
|
||||
asmAddress = null,
|
||||
isAsmSubroutine = false,
|
||||
inline = false,
|
||||
statements = statements.statements,
|
||||
position = ctx.toPosition()
|
||||
)
|
||||
}
|
||||
|
||||
override fun visitStatement_block(ctx: Statement_blockContext): AnonymousScope {
|
||||
val statements = ctx.statement().map { it.accept(this) as Statement }
|
||||
return AnonymousScope(statements.toMutableList(), ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitSub_param(pctx: Sub_paramContext): SubroutineParameter {
|
||||
val decl = pctx.vardecl()
|
||||
val tags = decl.TAG().map { t -> t.text }
|
||||
val validTags = arrayOf("@zp", "@requirezp", "@nozp", "@split", "@nosplit", "@shared")
|
||||
for(tag in tags) {
|
||||
if(tag !in validTags)
|
||||
throw SyntaxError("invalid parameter tag '$tag'", pctx.toPosition())
|
||||
}
|
||||
val zp = getZpOption(tags)
|
||||
val decldt = decl.datatype()
|
||||
var datatype = if(decldt!=null) dataTypeFor(decldt) else DataType.UNDEFINED
|
||||
if(decl.ARRAYSIG()!=null || decl.arrayindex()!=null)
|
||||
datatype = datatype.elementToArray()
|
||||
|
||||
val identifiers = decl.identifier()
|
||||
if(identifiers.size>1)
|
||||
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
|
||||
val identifiername = getname(identifiers[0])
|
||||
|
||||
val (registerorpair, statusregister) = parseParamRegister(pctx.register, pctx.toPosition())
|
||||
if(statusregister!=null) {
|
||||
throw SyntaxError("can't use status register as param for normal subroutines", Position(pctx.toPosition().file, pctx.register.line, pctx.register.charPositionInLine, pctx.register.charPositionInLine+1))
|
||||
}
|
||||
return SubroutineParameter(identifiername, datatype, zp, registerorpair, pctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitAsmsubroutine(ctx: AsmsubroutineContext): Subroutine {
|
||||
val inline = ctx.INLINE()!=null
|
||||
val ad = asmSubDecl(ctx.asmsub_decl())
|
||||
val statements = ctx.statement_block().accept(this) as AnonymousScope
|
||||
|
||||
return Subroutine(ad.name,
|
||||
ad.parameters.toMutableList(),
|
||||
ad.returntypes.toMutableList(),
|
||||
ad.asmParameterRegisters,
|
||||
ad.asmReturnvaluesRegisters,
|
||||
ad.asmClobbers, null, true, inline,
|
||||
statements = statements.statements, position = ctx.toPosition()
|
||||
)
|
||||
}
|
||||
|
||||
override fun visitExtsubroutine(ctx: ExtsubroutineContext): Subroutine {
|
||||
val subdecl = asmSubDecl(ctx.asmsub_decl())
|
||||
val constbank = (ctx.constbank?.accept(this) as NumericLiteral?)?.number?.toUInt()?.toUByte()
|
||||
val varbank = ctx.varbank?.accept(this) as IdentifierReference?
|
||||
val addr = ctx.address.accept(this) as Expression
|
||||
val address = Subroutine.Address(constbank, varbank, addr)
|
||||
return Subroutine(subdecl.name, subdecl.parameters.toMutableList(), subdecl.returntypes.toMutableList(),
|
||||
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
|
||||
subdecl.asmClobbers, address, true, inline = false, statements = mutableListOf(), position = ctx.toPosition()
|
||||
)
|
||||
}
|
||||
|
||||
override fun visitIf_stmt(ctx: If_stmtContext): IfElse {
|
||||
val condition = ctx.expression().accept(this) as Expression
|
||||
val truepart = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
|
||||
val elsepart = ctx.else_part()?.accept(this) as AnonymousScope? ?: AnonymousScope.empty()
|
||||
return IfElse(condition, truepart, elsepart, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitElse_part(ctx: Else_partContext): AnonymousScope {
|
||||
return stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
|
||||
}
|
||||
|
||||
override fun visitIf_expression(ctx: If_expressionContext): IfExpression {
|
||||
val (condition, truevalue, falsevalue) = ctx.expression().map { it.accept(this) as Expression }
|
||||
return IfExpression(condition, truevalue, falsevalue, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitBranch_stmt(ctx: Branch_stmtContext): ConditionalBranch {
|
||||
val branchcondition = branchCondition(ctx.branchcondition())
|
||||
val truepart = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
|
||||
val elsepart = ctx.else_part()?.accept(this) as AnonymousScope? ?: AnonymousScope.empty()
|
||||
return ConditionalBranch(branchcondition, truepart, elsepart, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitForloop(ctx: ForloopContext): ForLoop {
|
||||
val loopvar = ctx.scoped_identifier().accept(this) as IdentifierReference
|
||||
val iterable = ctx.expression().accept(this) as Expression
|
||||
val scope = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
|
||||
return ForLoop(loopvar, iterable, scope, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitWhileloop(ctx: WhileloopContext): WhileLoop {
|
||||
val condition = ctx.expression().accept(this) as Expression
|
||||
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
|
||||
return WhileLoop(condition, statements, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitUntilloop(ctx: UntilloopContext): UntilLoop {
|
||||
val condition = ctx.expression().accept(this) as Expression
|
||||
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
|
||||
return UntilLoop(statements, condition, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitRepeatloop(ctx: RepeatloopContext): RepeatLoop {
|
||||
val iterations = ctx.expression()?.accept(this) as Expression?
|
||||
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
|
||||
return RepeatLoop(iterations, statements, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitUnrollloop(ctx: UnrollloopContext): UnrollLoop {
|
||||
val iterations = ctx.expression().accept(this) as Expression
|
||||
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
|
||||
return UnrollLoop(iterations, statements, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitWhenstmt(ctx: WhenstmtContext): When {
|
||||
val condition = ctx.expression().accept(this) as Expression
|
||||
val choices = ctx.when_choice()?.map { it.accept(this) as WhenChoice }?.toMutableList() ?: mutableListOf()
|
||||
return When(condition, choices, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitWhen_choice(ctx: When_choiceContext): WhenChoice {
|
||||
val values = ctx.expression_list()?.expression()?.map { it.accept(this) as Expression }
|
||||
val statements = stmtBlockOrSingle(ctx.statement_block(), ctx.statement())
|
||||
return WhenChoice(values?.toMutableList(), statements, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitOngoto(ctx: OngotoContext): OnGoto {
|
||||
val elsepart = ctx.else_part()?.accept(this) as AnonymousScope? ?: AnonymousScope.empty()
|
||||
val isCall = ctx.kind.text == "call"
|
||||
val index = ctx.expression().accept(this) as Expression
|
||||
val labels = ctx.directivenamelist().scoped_identifier().map { it.accept(this) as IdentifierReference }
|
||||
return OnGoto(isCall, index, labels, elsepart, ctx.toPosition())
|
||||
}
|
||||
|
||||
|
||||
override fun visitModule_element(ctx: Module_elementContext): Node = visitChildren(ctx)
|
||||
override fun visitBlock_statement(ctx: Block_statementContext): Statement = visitChildren(ctx) as Statement
|
||||
override fun visitStatement(ctx: StatementContext): Statement = visitChildren(ctx) as Statement
|
||||
override fun visitVariabledeclaration(ctx: VariabledeclarationContext): VarDecl = visitChildren(ctx) as VarDecl
|
||||
override fun visitLiteralvalue(ctx: LiteralvalueContext): Expression = visitChildren(ctx) as Expression
|
||||
|
||||
|
||||
override fun visitDirectivenamelist(ctx: DirectivenamelistContext) = throw FatalAstException("should not be called")
|
||||
override fun visitAsmsub_decl(ctx: Asmsub_declContext?) = throw FatalAstException("should not be called")
|
||||
override fun visitAsmsub_params(ctx: Asmsub_paramsContext) = throw FatalAstException("should not be called")
|
||||
override fun visitExpression_list(ctx: Expression_listContext) = throw FatalAstException("should not be called")
|
||||
override fun visitBranchcondition(ctx: BranchconditionContext) = throw FatalAstException("should not be called")
|
||||
override fun visitDatatype(ctx: DatatypeContext) = throw FatalAstException("should not be called")
|
||||
override fun visitSizeof_argument(ctx: Sizeof_argumentContext) = throw FatalAstException("should not be called")
|
||||
override fun visitReturnvalues(ctx: ReturnvaluesContext) = throw FatalAstException("should not be called")
|
||||
override fun visitTypecast(ctx: TypecastContext) = throw FatalAstException("should not be called")
|
||||
override fun visitSub_params(ctx: Sub_paramsContext) = throw FatalAstException("should not be called")
|
||||
override fun visitAsmsub_param(ctx: Asmsub_paramContext) = throw FatalAstException("should not be called")
|
||||
override fun visitAsmsub_clobbers(ctx: Asmsub_clobbersContext) = throw FatalAstException("should not be called")
|
||||
override fun visitClobber(ctx: ClobberContext) = throw FatalAstException("should not be called")
|
||||
override fun visitAsmsub_returns(ctx: Asmsub_returnsContext) = throw FatalAstException("should not be called")
|
||||
override fun visitAsmsub_return(ctx: Asmsub_returnContext) = throw FatalAstException("should not be called")
|
||||
override fun visitSub_return_part(ctx: Sub_return_partContext) = throw FatalAstException("should not be called")
|
||||
|
||||
|
||||
private fun getname(identifier: IdentifierContext): String = identifier.children[0].text
|
||||
|
||||
private fun ParserRuleContext.toPosition() : Position {
|
||||
val pathString = start.inputStream.sourceName
|
||||
val filename = if(SourceCode.isRegularFilesystemPath(pathString)) {
|
||||
val path = Path(pathString)
|
||||
if(path.isRegularFile()) {
|
||||
SourceCode.relative(path).toString()
|
||||
} else {
|
||||
path.toString()
|
||||
}
|
||||
} else {
|
||||
pathString
|
||||
}
|
||||
// note: beware of TAB characters in the source text, they count as 1 column...
|
||||
return Position(filename, start.line, start.charPositionInLine+1, start.charPositionInLine + 1 + start.stopIndex - start.startIndex)
|
||||
}
|
||||
|
||||
private fun getZpOption(tags: List<String>): ZeropageWish = when {
|
||||
"@requirezp" in tags -> ZeropageWish.REQUIRE_ZEROPAGE
|
||||
"@zp" in tags -> ZeropageWish.PREFER_ZEROPAGE
|
||||
"@nozp" in tags -> ZeropageWish.NOT_IN_ZEROPAGE
|
||||
else -> ZeropageWish.DONTCARE
|
||||
}
|
||||
|
||||
private fun getSplitOption(tags: List<String>): SplitWish {
|
||||
return when {
|
||||
"@nosplit" in tags -> SplitWish.NOSPLIT
|
||||
"@split" in tags -> SplitWish.SPLIT
|
||||
else -> SplitWish.DONTCARE
|
||||
}
|
||||
}
|
||||
|
||||
private fun asmSubroutineParam(pctx: Asmsub_paramContext): AsmSubroutineParameter {
|
||||
val vardecl = pctx.vardecl()
|
||||
val decldt = vardecl.datatype()
|
||||
var datatype = if(decldt!=null) dataTypeFor(decldt) else DataType.UNDEFINED
|
||||
if(vardecl.ARRAYSIG()!=null || vardecl.arrayindex()!=null)
|
||||
datatype = datatype.elementToArray()
|
||||
val (registerorpair, statusregister) = parseParamRegister(pctx.register, pctx.toPosition())
|
||||
val identifiers = vardecl.identifier()
|
||||
if(identifiers.size>1)
|
||||
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
|
||||
val identifiername = getname(identifiers[0])
|
||||
return AsmSubroutineParameter(identifiername, datatype, registerorpair, statusregister, pctx.toPosition())
|
||||
}
|
||||
|
||||
private fun parseParamRegister(registerTok: Token?, pos: Position): Pair<RegisterOrPair?, Statusflag?> {
|
||||
if(registerTok==null)
|
||||
return Pair(null, null)
|
||||
val register = registerTok.text
|
||||
var registerorpair: RegisterOrPair? = null
|
||||
var statusregister: Statusflag? = null
|
||||
if(register!=null) {
|
||||
when (register) {
|
||||
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(register)
|
||||
in Statusflag.names -> statusregister = Statusflag.valueOf(register)
|
||||
else -> {
|
||||
throw SyntaxError("invalid register or status flag", Position(pos.file, registerTok.line, registerTok.charPositionInLine, registerTok.charPositionInLine+1))
|
||||
}
|
||||
}
|
||||
}
|
||||
return Pair(registerorpair, statusregister)
|
||||
}
|
||||
|
||||
private fun asmReturn(rctx: Asmsub_returnContext): AsmSubroutineReturn {
|
||||
val register = rctx.register.text
|
||||
var registerorpair: RegisterOrPair? = null
|
||||
var statusregister: Statusflag? = null
|
||||
if(register!=null) {
|
||||
when (register) {
|
||||
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(register)
|
||||
in Statusflag.names -> statusregister = Statusflag.valueOf(register)
|
||||
else -> throw SyntaxError("invalid register or status flag", rctx.toPosition())
|
||||
}
|
||||
}
|
||||
return AsmSubroutineReturn(
|
||||
dataTypeFor(rctx.datatype()),
|
||||
registerorpair,
|
||||
statusregister)
|
||||
}
|
||||
|
||||
private fun cpuRegister(text: String, pos: Position): CpuRegister {
|
||||
try {
|
||||
return CpuRegister.valueOf(text)
|
||||
} catch(_: IllegalArgumentException) {
|
||||
throw SyntaxError("invalid cpu register", pos)
|
||||
}
|
||||
}
|
||||
|
||||
private fun dataTypeFor(it: DatatypeContext) = DataType.forDt(baseDatatypeFor(it))
|
||||
|
||||
private fun baseDatatypeFor(it: DatatypeContext) = BaseDataType.valueOf(it.text.uppercase())
|
||||
|
||||
private fun stmtBlockOrSingle(statementBlock: Statement_blockContext?, statement: StatementContext?): AnonymousScope {
|
||||
return if(statementBlock!=null)
|
||||
statementBlock.accept(this) as AnonymousScope
|
||||
else if(statement!=null)
|
||||
AnonymousScope(mutableListOf(statement.accept(this) as Statement), statement.toPosition())
|
||||
else
|
||||
AnonymousScope.empty()
|
||||
}
|
||||
|
||||
private fun branchCondition(ctx: BranchconditionContext) = BranchCondition.valueOf(ctx.text.substringAfter('_').uppercase())
|
||||
|
||||
private fun asmSubDecl(ad: Asmsub_declContext): AsmsubDecl {
|
||||
val name = getname(ad.identifier())
|
||||
val params = ad.asmsub_params()?.asmsub_param()?.map { asmSubroutineParam(it) } ?: emptyList()
|
||||
val returns = ad.asmsub_returns()?.asmsub_return()?.map { asmReturn(it) } ?: emptyList()
|
||||
val clobbers = ad.asmsub_clobbers()?.clobber()?.NAME()?.map { cpuRegister(it.text, ad.toPosition()) } ?: emptyList()
|
||||
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.zp, it.registerOrPair, it.position) }
|
||||
val normalReturntypes = returns.map { it.type }
|
||||
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
|
||||
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
|
||||
return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers.toSet())
|
||||
}
|
||||
|
||||
private class AsmsubDecl(val name: String,
|
||||
val parameters: List<SubroutineParameter>,
|
||||
val returntypes: List<DataType>,
|
||||
val asmParameterRegisters: List<RegisterOrStatusflag>,
|
||||
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
|
||||
val asmClobbers: Set<CpuRegister>)
|
||||
|
||||
private class AsmSubroutineParameter(name: String,
|
||||
type: DataType,
|
||||
registerOrPair: RegisterOrPair?,
|
||||
val statusflag: Statusflag?,
|
||||
position: Position) : SubroutineParameter(name, type, ZeropageWish.DONTCARE, registerOrPair, position)
|
||||
|
||||
private class AsmSubroutineReturn(val type: DataType,
|
||||
val registerOrPair: RegisterOrPair?,
|
||||
val statusflag: Statusflag?)
|
||||
}
|
@ -151,8 +151,7 @@ class BinaryExpression(
|
||||
var left: Expression,
|
||||
var operator: String,
|
||||
var right: Expression,
|
||||
override val position: Position,
|
||||
private val insideParentheses: Boolean = false
|
||||
override val position: Position
|
||||
) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -172,7 +171,7 @@ class BinaryExpression(
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun copy() = BinaryExpression(left.copy(), operator, right.copy(), position, insideParentheses)
|
||||
override fun copy() = BinaryExpression(left.copy(), operator, right.copy(), position)
|
||||
override fun toString() = "[$left $operator $right]"
|
||||
|
||||
override val isSimple = false
|
||||
|
@ -239,19 +239,20 @@ enum class VarDeclType {
|
||||
MEMORY
|
||||
}
|
||||
|
||||
class VarDecl(val type: VarDeclType,
|
||||
val origin: VarDeclOrigin,
|
||||
val datatype: DataType,
|
||||
val zeropage: ZeropageWish,
|
||||
val splitwordarray: SplitWish,
|
||||
var arraysize: ArrayIndex?,
|
||||
override val name: String,
|
||||
val names: List<String>,
|
||||
var value: Expression?,
|
||||
val sharedWithAsm: Boolean,
|
||||
val alignment: UInt,
|
||||
val dirty: Boolean,
|
||||
override val position: Position) : Statement(), INamedStatement {
|
||||
class VarDecl(
|
||||
var type: VarDeclType,
|
||||
val origin: VarDeclOrigin,
|
||||
val datatype: DataType,
|
||||
val zeropage: ZeropageWish,
|
||||
val splitwordarray: SplitWish,
|
||||
var arraysize: ArrayIndex?,
|
||||
override val name: String,
|
||||
val names: List<String>,
|
||||
var value: Expression?,
|
||||
val sharedWithAsm: Boolean,
|
||||
val alignment: UInt,
|
||||
val dirty: Boolean,
|
||||
override val position: Position) : Statement(), INamedStatement {
|
||||
override lateinit var parent: Node
|
||||
var allowInitializeWithZero = true
|
||||
|
||||
@ -830,6 +831,10 @@ class AnonymousScope(override val statements: MutableList<Statement>,
|
||||
statements.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun empty(pos: Position?=null): AnonymousScope = AnonymousScope(mutableListOf(), pos ?: Position.DUMMY)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Statement)
|
||||
val idx = statements.indexOfFirst { it===node }
|
||||
|
@ -2,9 +2,7 @@ package prog8.parser
|
||||
|
||||
import org.antlr.v4.runtime.*
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.antlr.toAst
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.antlr.Antlr2KotlinVisitor
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.source.SourceCode
|
||||
|
||||
@ -25,41 +23,12 @@ object Prog8Parser {
|
||||
parser.addErrorListener(antlrErrorListener)
|
||||
|
||||
val parseTree = parser.module()
|
||||
val module = ParsedModule(src)
|
||||
|
||||
parseTree.module_element().forEach {
|
||||
val block = it.block()?.toAst(module.isLibrary)
|
||||
val directive = it.directive()?.toAst()
|
||||
if(directive != null) module.add(directive)
|
||||
if(block != null) module.add(block)
|
||||
}
|
||||
|
||||
return module
|
||||
val visitor = Antlr2KotlinVisitor(src)
|
||||
val visitorResult = visitor.visit(parseTree)
|
||||
return visitorResult as Module
|
||||
}
|
||||
|
||||
private class ParsedModule(source: SourceCode) :
|
||||
Module(mutableListOf(), Position(source.origin, 1, 0, 0), source)
|
||||
{
|
||||
|
||||
/**
|
||||
* Adds a [Directive] to [statements] and
|
||||
* sets this Module as its [parent].
|
||||
* Note: you can only add [Directive]s or [Block]s to a Module.
|
||||
*/
|
||||
fun add(child: Directive) {
|
||||
child.linkParents(this)
|
||||
statements.add(child)
|
||||
}
|
||||
/**
|
||||
* Adds a [Block] to [statements] and
|
||||
* sets this Module as its [parent].
|
||||
* Note: you can only add [Directive]s or [Block]s to a Module.
|
||||
*/
|
||||
fun add(child: Block) {
|
||||
child.linkParents(this)
|
||||
statements.add(child)
|
||||
}
|
||||
}
|
||||
|
||||
private object Prog8ErrorStrategy: BailErrorStrategy() {
|
||||
private fun fillIn(e: RecognitionException?, ctx: ParserRuleContext?) {
|
||||
|
@ -362,15 +362,13 @@ Read the `conv source code <https://github.com/irmen/prog8/tree/master/compiler/
|
||||
to see what's in there.
|
||||
|
||||
|
||||
coroutines (experimental)
|
||||
-------------------------
|
||||
coroutines
|
||||
----------
|
||||
Provides a system to make cooperative multitasking programs via coroutines.
|
||||
A 'coroutine' is a subroutine whose execution you can pause and resume.
|
||||
This library handles the voodoo for you to switch between such coroutines transparently,
|
||||
so it can seem that your program is executing many subroutines at the same time.
|
||||
|
||||
API is experimental and may change or disappear in a future version.
|
||||
|
||||
Read the `coroutines source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/coroutines.p8>`_
|
||||
to see what's in there. And look at the ``multitasking`` example to see how it can be used.
|
||||
Here is a minimal example (if the library gets more stable, better docs will be written here)::
|
||||
@ -1178,9 +1176,7 @@ to see what's in there. (Note: slight variations for different compiler targets)
|
||||
|
||||
verafx (cx16 only)
|
||||
-------------------
|
||||
Available for the Cx16 target.
|
||||
Experimental routines that use the new Vera FX logic (hopefully coming in the Vera in new X16 boards,
|
||||
the emulators already support it).
|
||||
Available for the Cx16 target. Routines that use the Vera FX logic to accelerate certain operations.
|
||||
|
||||
``available``
|
||||
Returns true if Vera FX is available, false if not (that would be an older Vera chip)
|
||||
|
@ -24,7 +24,6 @@ Comments
|
||||
Everything on the line after a semicolon ``;`` is a comment and is ignored by the compiler.
|
||||
If the whole line is just a comment, this line will be copied into the resulting assembly source code for reference.
|
||||
There's also a block-comment: everything surrounded with ``/*`` and ``*/`` is ignored and this can span multiple lines.
|
||||
This block comment is experimental for now: it may change or even be removed again in a future compiler version.
|
||||
The recommended way to comment out a bunch of lines remains to just bulk comment them individually with ``;``.
|
||||
|
||||
Directive
|
||||
|
@ -10,9 +10,7 @@ Idea is to make it feature complete in the IR/Virtual target, then merge it to m
|
||||
Future Things and Ideas
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- is "checkAssignmentCompatible" redundant (gets called just 1 time!) when we also have "checkValueTypeAndRange" ?
|
||||
- enums?
|
||||
- romable: should we have a way to explicitly set the memory address for the BSS area (instead of only the highram bank number on X16, allow a memory address too for the -varshigh option?)
|
||||
- romable: should we have a way to explicitly set the memory address for the BSS area (add a -varsaddress and -slabsaddress options?)
|
||||
- romable: fix remaining codegens (some for loops, see ForLoopsAsmGen)
|
||||
- Kotlin: can we use inline value classes in certain spots?
|
||||
- add float support to the configurable compiler targets
|
||||
@ -25,7 +23,7 @@ Future Things and Ideas
|
||||
Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEvaluating() and argumentsViaRegisters().
|
||||
- Does it make codegen easier if everything is an expression? Start with the PtProgram ast classes, change statements to expressions that have (new) VOID data type
|
||||
- Can we support signed % (remainder) somehow?
|
||||
- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays. Probaby only useful if we have typed pointers. (addressed in 'struct' branch)
|
||||
- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays. Probaby only useful once we have typed pointers. (addressed in 'struct' branch)
|
||||
- make a form of "manual generics" possible like: varsub routine(T arg)->T where T is expanded to a specific type
|
||||
(this is already done hardcoded for several of the builtin functions)
|
||||
- [much work:] more support for (64tass) SEGMENTS in the prog8 syntax itself?
|
||||
@ -61,7 +59,6 @@ IR/VM
|
||||
Libraries
|
||||
---------
|
||||
- Add split-word array sorting routines to sorting module?
|
||||
- Add double-array sorting routines to sorting module? (that allows you to sort a second array in sync with the array of numbers)
|
||||
- See if the raster interrupt handler on the C64 can be tweaked to be a more stable raster irq
|
||||
- pet32 target: make syslib more complete (missing kernal routines)?
|
||||
- need help with: PET disk routines (OPEN, SETLFS etc are not exposed as kernal calls)
|
||||
|
@ -384,6 +384,18 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
|
||||
%asm {{
|
||||
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
|
||||
cpx #0
|
||||
bne +
|
||||
dey
|
||||
+ dex
|
||||
tya
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pop() -> ubyte @A {
|
||||
%asm {{
|
||||
pla
|
||||
|
@ -317,6 +317,18 @@ save_SCRATCH_ZPWORD2 .word ?
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub get_as_returnaddress(uword address @XY) -> uword @AX {
|
||||
%asm {{
|
||||
; return the address like JSR would push onto the stack: address-1, MSB first then LSB
|
||||
cpx #0
|
||||
bne +
|
||||
dey
|
||||
+ dex
|
||||
tya
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub pop() -> ubyte @A {
|
||||
%asm {{
|
||||
pla
|
||||
|
810
examples/test.p8
810
examples/test.p8
@ -1,800 +1,18 @@
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
txt.plot(0, 49)
|
||||
bytesoverflow()
|
||||
bytesoverflow_jump()
|
||||
bytesoverflow_jump_indirect()
|
||||
bytessmall()
|
||||
bytessmall_jump()
|
||||
bytessmall_jump_indirect()
|
||||
bytes99()
|
||||
bytes100()
|
||||
bytes101()
|
||||
words()
|
||||
zerobytes()
|
||||
zerowords()
|
||||
}
|
||||
|
||||
sub zerobytes() {
|
||||
byte @shared sb = -100
|
||||
byte @shared p = 0
|
||||
const byte cb = -100
|
||||
|
||||
txt.print("\nsigned bytes with 0\n")
|
||||
txt.print("expected: ")
|
||||
txt.print_b(cb)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>0)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>=0)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<0)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<=0)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" calc'd: ")
|
||||
txt.print_b(sb)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>0)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>=0)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<0)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<=0)
|
||||
txt.nl()
|
||||
|
||||
txt.print("calc'd 2: ")
|
||||
txt.print_b(sb)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>=p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<=p)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" if stmt: ")
|
||||
txt.print_b(sb)
|
||||
txt.spc()
|
||||
if sb>0 txt.print("true ") else txt.print("false ")
|
||||
if sb>=0 txt.print("true ") else txt.print("false ")
|
||||
if sb<0 txt.print("true ") else txt.print("false ")
|
||||
if sb<=0 txt.print("true ") else txt.print("false ")
|
||||
txt.nl()
|
||||
|
||||
txt.print(" ifstmt2: ")
|
||||
txt.print_b(sb)
|
||||
txt.spc()
|
||||
if sb>p txt.print("true ") else txt.print("false ")
|
||||
if sb>=p txt.print("true ") else txt.print("false ")
|
||||
if sb<p txt.print("true ") else txt.print("false ")
|
||||
if sb<=p txt.print("true ") else txt.print("false ")
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub zerowords() {
|
||||
word @shared sbw = -30000
|
||||
word @shared pw = 0
|
||||
const word cbw = -30000
|
||||
|
||||
txt.print("\nsigned words\n")
|
||||
txt.print("expected: ")
|
||||
txt.print_w(cbw)
|
||||
txt.spc()
|
||||
txt.print_bool(cbw>0)
|
||||
txt.spc()
|
||||
txt.print_bool(cbw>=0)
|
||||
txt.spc()
|
||||
txt.print_bool(cbw<0)
|
||||
txt.spc()
|
||||
txt.print_bool(cbw<=0)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" calc'd: ")
|
||||
txt.print_w(sbw)
|
||||
txt.spc()
|
||||
txt.print_bool(sbw>0)
|
||||
txt.spc()
|
||||
txt.print_bool(sbw>=0)
|
||||
txt.spc()
|
||||
txt.print_bool(sbw<0)
|
||||
txt.spc()
|
||||
txt.print_bool(sbw<=0)
|
||||
txt.nl()
|
||||
|
||||
txt.print("calc'd 2: ")
|
||||
txt.print_w(sbw)
|
||||
txt.spc()
|
||||
txt.print_bool(sbw>pw)
|
||||
txt.spc()
|
||||
txt.print_bool(sbw>=pw)
|
||||
txt.spc()
|
||||
txt.print_bool(sbw<pw)
|
||||
txt.spc()
|
||||
txt.print_bool(sbw<=pw)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" if stmt: ")
|
||||
txt.print_w(sbw)
|
||||
txt.spc()
|
||||
if sbw>0 txt.print("true ") else txt.print("false ")
|
||||
if sbw>=0 txt.print("true ") else txt.print("false ")
|
||||
if sbw<0 txt.print("true ") else txt.print("false ")
|
||||
if sbw<=0 txt.print("true ") else txt.print("false ")
|
||||
txt.nl()
|
||||
|
||||
txt.print(" ifstmt2: ")
|
||||
txt.print_w(sbw)
|
||||
txt.spc()
|
||||
if sbw>pw txt.print("true ") else txt.print("false ")
|
||||
if sbw>=pw txt.print("true ") else txt.print("false ")
|
||||
if sbw<pw txt.print("true ") else txt.print("false ")
|
||||
if sbw<=pw txt.print("true ") else txt.print("false ")
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub bytes99() {
|
||||
const byte cb = 99
|
||||
byte @shared sb = 99
|
||||
byte @shared p = 100
|
||||
|
||||
txt.print("\nsigned bytes, 99\n")
|
||||
txt.print("expected: ")
|
||||
txt.print_b(100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>=100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<=100)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" calc'd: ")
|
||||
txt.print_b(100)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>100)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>=100)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<100)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<=100)
|
||||
txt.nl()
|
||||
|
||||
txt.print("calc'd 2: ")
|
||||
txt.print_b(p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>=p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<=p)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" if stmt: ")
|
||||
txt.print_b(100)
|
||||
txt.spc()
|
||||
if sb>100 txt.print("true ") else txt.print("false ")
|
||||
if sb>=100 txt.print("true ") else txt.print("false ")
|
||||
if sb<100 txt.print("true ") else txt.print("false ")
|
||||
if sb<=100 txt.print("true ") else txt.print("false ")
|
||||
txt.nl()
|
||||
|
||||
txt.print(" ifstmt2: ")
|
||||
txt.print_b(p)
|
||||
txt.spc()
|
||||
if sb>p txt.print("true ") else txt.print("false ")
|
||||
if sb>=p txt.print("true ") else txt.print("false ")
|
||||
if sb<p txt.print("true ") else txt.print("false ")
|
||||
if sb<=p txt.print("true ") else txt.print("false ")
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub bytes100() {
|
||||
const byte cb = 100
|
||||
byte @shared sb = 100
|
||||
byte @shared p = 100
|
||||
|
||||
txt.print("\nsigned bytes, 100\n")
|
||||
txt.print("expected: ")
|
||||
txt.print_b(100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>=100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<=100)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" calc'd: ")
|
||||
txt.print_b(100)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>100)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>=100)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<100)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<=100)
|
||||
txt.nl()
|
||||
|
||||
txt.print("calc'd 2: ")
|
||||
txt.print_b(p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>=p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<=p)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" if stmt: ")
|
||||
txt.print_b(100)
|
||||
txt.spc()
|
||||
if sb>100 txt.print("true ") else txt.print("false ")
|
||||
if sb>=100 txt.print("true ") else txt.print("false ")
|
||||
if sb<100 txt.print("true ") else txt.print("false ")
|
||||
if sb<=100 txt.print("true ") else txt.print("false ")
|
||||
txt.nl()
|
||||
|
||||
txt.print(" ifstmt2: ")
|
||||
txt.print_b(p)
|
||||
txt.spc()
|
||||
if sb>p txt.print("true ") else txt.print("false ")
|
||||
if sb>=p txt.print("true ") else txt.print("false ")
|
||||
if sb<p txt.print("true ") else txt.print("false ")
|
||||
if sb<=p txt.print("true ") else txt.print("false ")
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub bytes101() {
|
||||
const byte cb = 101
|
||||
byte @shared sb = 101
|
||||
byte @shared p = 100
|
||||
|
||||
txt.print("\nsigned bytes, 101\n")
|
||||
txt.print("expected: ")
|
||||
txt.print_b(100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>=100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<=100)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" calc'd: ")
|
||||
txt.print_b(100)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>100)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>=100)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<100)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<=100)
|
||||
txt.nl()
|
||||
|
||||
txt.print("calc'd 2: ")
|
||||
txt.print_b(p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>=p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<=p)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" if stmt: ")
|
||||
txt.print_b(100)
|
||||
txt.spc()
|
||||
if sb>100 txt.print("true ") else txt.print("false ")
|
||||
if sb>=100 txt.print("true ") else txt.print("false ")
|
||||
if sb<100 txt.print("true ") else txt.print("false ")
|
||||
if sb<=100 txt.print("true ") else txt.print("false ")
|
||||
txt.nl()
|
||||
|
||||
txt.print(" ifstmt2: ")
|
||||
txt.print_b(p)
|
||||
txt.spc()
|
||||
if sb>p txt.print("true ") else txt.print("false ")
|
||||
if sb>=p txt.print("true ") else txt.print("false ")
|
||||
if sb<p txt.print("true ") else txt.print("false ")
|
||||
if sb<=p txt.print("true ") else txt.print("false ")
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub bytesoverflow() {
|
||||
byte @shared sb = -100
|
||||
byte @shared p = 100
|
||||
const byte cb = -100
|
||||
|
||||
txt.print("\nsigned bytes, overflow\n")
|
||||
txt.print("expected: ")
|
||||
txt.print_b(100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>=100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<=100)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" calc'd: ")
|
||||
txt.print_b(100)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>100)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>=100)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<100)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<=100)
|
||||
txt.nl()
|
||||
|
||||
txt.print("calc'd 2: ")
|
||||
txt.print_b(p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>=p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<=p)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" if stmt: ")
|
||||
txt.print_b(100)
|
||||
txt.spc()
|
||||
if sb>100 txt.print("true ") else txt.print("false ")
|
||||
if sb>=100 txt.print("true ") else txt.print("false ")
|
||||
if sb<100 txt.print("true ") else txt.print("false ")
|
||||
if sb<=100 txt.print("true ") else txt.print("false ")
|
||||
txt.nl()
|
||||
|
||||
txt.print(" ifstmt2: ")
|
||||
txt.print_b(p)
|
||||
txt.spc()
|
||||
if sb>p txt.print("true ") else txt.print("false ")
|
||||
if sb>=p txt.print("true ") else txt.print("false ")
|
||||
if sb<p txt.print("true ") else txt.print("false ")
|
||||
if sb<=p txt.print("true ") else txt.print("false ")
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub bytessmall() {
|
||||
byte @shared sb = -10
|
||||
byte @shared p = 10
|
||||
const byte cb = -10
|
||||
|
||||
txt.print("\nsigned bytes, small value\n")
|
||||
txt.print("expected: ")
|
||||
txt.print_b(10)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>10)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>=10)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<10)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<=10)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" calc'd: ")
|
||||
txt.print_b(10)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>10)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>=10)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<10)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<=10)
|
||||
txt.nl()
|
||||
|
||||
txt.print("calc'd 2: ")
|
||||
txt.print_b(p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb>=p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<p)
|
||||
txt.spc()
|
||||
txt.print_bool(sb<=p)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" if stmt: ")
|
||||
txt.print_b(10)
|
||||
txt.spc()
|
||||
if sb>10 txt.print("true ") else txt.print("false ")
|
||||
if sb>=10 txt.print("true ") else txt.print("false ")
|
||||
if sb<10 txt.print("true ") else txt.print("false ")
|
||||
if sb<=10 txt.print("true ") else txt.print("false ")
|
||||
txt.nl()
|
||||
|
||||
txt.print(" ifstmt2: ")
|
||||
txt.print_b(p)
|
||||
txt.spc()
|
||||
if sb>p txt.print("true ") else txt.print("false ")
|
||||
if sb>=p txt.print("true ") else txt.print("false ")
|
||||
if sb<p txt.print("true ") else txt.print("false ")
|
||||
if sb<=p txt.print("true ") else txt.print("false ")
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub bytesoverflow_jump() {
|
||||
byte @shared sb = -100
|
||||
byte @shared p = 100
|
||||
const byte cb = -100
|
||||
|
||||
txt.print("\nsigned bytes, overflow, jmp after if\n")
|
||||
txt.print("expected: ")
|
||||
txt.print_b(100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>=100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<=100)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" calc'd: ")
|
||||
txt.print_b(100)
|
||||
txt.spc()
|
||||
if sb>100 goto jump1
|
||||
else { txt.print("false ") goto next1 }
|
||||
jump1:
|
||||
txt.print("true ")
|
||||
next1:
|
||||
if sb>=100 goto jump2
|
||||
else { txt.print("false ") goto next2 }
|
||||
jump2:
|
||||
txt.print("true ")
|
||||
next2:
|
||||
if sb<100 goto jump3
|
||||
else { txt.print("false ") goto next3 }
|
||||
jump3:
|
||||
txt.print("true ")
|
||||
next3:
|
||||
if sb<=100 goto jump4
|
||||
else { txt.print("false ") goto next4 }
|
||||
jump4:
|
||||
txt.print("true ")
|
||||
next4:
|
||||
txt.nl()
|
||||
|
||||
txt.print("calc'd 2: ")
|
||||
txt.print_b(p)
|
||||
txt.spc()
|
||||
if sb>p goto jump1b
|
||||
else { txt.print("false ") goto next1b }
|
||||
jump1b:
|
||||
txt.print("true ")
|
||||
next1b:
|
||||
if sb>=p goto jump2b
|
||||
else { txt.print("false ") goto next2b }
|
||||
jump2b:
|
||||
txt.print("true ")
|
||||
next2b:
|
||||
if sb<p goto jump3b
|
||||
else { txt.print("false ") goto next3b }
|
||||
jump3b:
|
||||
txt.print("true ")
|
||||
next3b:
|
||||
if sb<=p goto jump4b
|
||||
else { txt.print("false ") goto next4b }
|
||||
jump4b:
|
||||
txt.print("true ")
|
||||
next4b:
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub bytesoverflow_jump_indirect() {
|
||||
byte @shared sb = -100
|
||||
byte @shared p = 100
|
||||
const byte cb = -100
|
||||
|
||||
txt.print("\nsigned bytes, overflow, jmp indirect after if\n")
|
||||
txt.print("expected: ")
|
||||
txt.print_b(100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>=100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<100)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<=100)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" calc'd: ")
|
||||
txt.print_b(100)
|
||||
txt.spc()
|
||||
uword tgt = &jump1
|
||||
if sb>100 goto tgt
|
||||
else { txt.print("false ") goto next1 }
|
||||
jump1:
|
||||
txt.print("true ")
|
||||
next1:
|
||||
tgt = &jump2
|
||||
if sb>=100 goto tgt
|
||||
else { txt.print("false ") goto next2 }
|
||||
jump2:
|
||||
txt.print("true ")
|
||||
next2:
|
||||
tgt = &jump3
|
||||
if sb<100 goto tgt
|
||||
else { txt.print("false ") goto next3 }
|
||||
jump3:
|
||||
txt.print("true ")
|
||||
next3:
|
||||
tgt = &jump4
|
||||
if sb<=100 goto tgt
|
||||
else { txt.print("false ") goto next4 }
|
||||
jump4:
|
||||
txt.print("true ")
|
||||
next4:
|
||||
txt.nl()
|
||||
|
||||
txt.print("calc'd 2: ")
|
||||
txt.print_b(p)
|
||||
txt.spc()
|
||||
tgt = &jump1b
|
||||
if sb>p goto tgt
|
||||
else { txt.print("false ") goto next1b }
|
||||
jump1b:
|
||||
txt.print("true ")
|
||||
next1b:
|
||||
tgt = &jump2b
|
||||
if sb>=p goto tgt
|
||||
else { txt.print("false ") goto next2b }
|
||||
jump2b:
|
||||
txt.print("true ")
|
||||
next2b:
|
||||
tgt = &jump3b
|
||||
if sb<p goto tgt
|
||||
else { txt.print("false ") goto next3b }
|
||||
jump3b:
|
||||
txt.print("true ")
|
||||
next3b:
|
||||
tgt = &jump4b
|
||||
if sb<=p goto tgt
|
||||
else { txt.print("false ") goto next4b }
|
||||
jump4b:
|
||||
txt.print("true ")
|
||||
next4b:
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub bytessmall_jump() {
|
||||
byte @shared sb = -10
|
||||
byte @shared p = 10
|
||||
const byte cb = -10
|
||||
|
||||
txt.print("\nsigned bytes, small value, jmp after if\n")
|
||||
txt.print("expected: ")
|
||||
txt.print_b(10)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>10)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>=10)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<10)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<=10)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" calc'd: ")
|
||||
txt.print_b(10)
|
||||
txt.spc()
|
||||
if sb>10 goto jump1
|
||||
else { txt.print("false ") goto next1 }
|
||||
jump1:
|
||||
txt.print("true ")
|
||||
next1:
|
||||
if sb>=10 goto jump2
|
||||
else { txt.print("false ") goto next2 }
|
||||
jump2:
|
||||
txt.print("true ")
|
||||
next2:
|
||||
if sb<10 goto jump3
|
||||
else { txt.print("false ") goto next3 }
|
||||
jump3:
|
||||
txt.print("true ")
|
||||
next3:
|
||||
if sb<=10 goto jump4
|
||||
else { txt.print("false ") goto next4 }
|
||||
jump4:
|
||||
txt.print("true ")
|
||||
next4:
|
||||
txt.nl()
|
||||
|
||||
txt.print("calc'd 2: ")
|
||||
txt.print_b(p)
|
||||
txt.spc()
|
||||
if sb>p goto jump1b
|
||||
else { txt.print("false ") goto next1b }
|
||||
jump1b:
|
||||
txt.print("true ")
|
||||
next1b:
|
||||
if sb>=p goto jump2b
|
||||
else { txt.print("false ") goto next2b }
|
||||
jump2b:
|
||||
txt.print("true ")
|
||||
next2b:
|
||||
if sb<p goto jump3b
|
||||
else { txt.print("false ") goto next3b }
|
||||
jump3b:
|
||||
txt.print("true ")
|
||||
next3b:
|
||||
if sb<=p goto jump4b
|
||||
else { txt.print("false ") goto next4b }
|
||||
jump4b:
|
||||
txt.print("true ")
|
||||
next4b:
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub bytessmall_jump_indirect() {
|
||||
byte @shared sb = -10
|
||||
byte @shared p = 10
|
||||
const byte cb = -10
|
||||
|
||||
txt.print("\nsigned bytes, small value, jmp indirect after if\n")
|
||||
txt.print("expected: ")
|
||||
txt.print_b(10)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>10)
|
||||
txt.spc()
|
||||
txt.print_bool(cb>=10)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<10)
|
||||
txt.spc()
|
||||
txt.print_bool(cb<=10)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" calc'd: ")
|
||||
txt.print_b(10)
|
||||
txt.spc()
|
||||
uword tgt = &jump1
|
||||
if sb>10 goto tgt
|
||||
else { txt.print("false ") goto next1 }
|
||||
jump1:
|
||||
txt.print("true ")
|
||||
next1:
|
||||
tgt = &jump2
|
||||
if sb>=10 goto tgt
|
||||
else { txt.print("false ") goto next2 }
|
||||
jump2:
|
||||
txt.print("true ")
|
||||
next2:
|
||||
tgt = &jump3
|
||||
if sb<10 goto tgt
|
||||
else { txt.print("false ") goto next3 }
|
||||
jump3:
|
||||
txt.print("true ")
|
||||
next3:
|
||||
tgt = &jump4
|
||||
if sb<=10 goto tgt
|
||||
else { txt.print("false ") goto next4 }
|
||||
jump4:
|
||||
txt.print("true ")
|
||||
next4:
|
||||
txt.nl()
|
||||
|
||||
txt.print("calc'd 2: ")
|
||||
txt.print_b(p)
|
||||
txt.spc()
|
||||
tgt = &jump1b
|
||||
if sb>p goto tgt
|
||||
else { txt.print("false ") goto next1b }
|
||||
jump1b:
|
||||
txt.print("true ")
|
||||
next1b:
|
||||
tgt = &jump2b
|
||||
if sb>=p goto tgt
|
||||
else { txt.print("false ") goto next2b }
|
||||
jump2b:
|
||||
txt.print("true ")
|
||||
next2b:
|
||||
tgt = &jump3b
|
||||
if sb<p goto tgt
|
||||
else { txt.print("false ") goto next3b }
|
||||
jump3b:
|
||||
txt.print("true ")
|
||||
next3b:
|
||||
tgt = &jump4b
|
||||
if sb<=p goto tgt
|
||||
else { txt.print("false ") goto next4b }
|
||||
jump4b:
|
||||
txt.print("true ")
|
||||
next4b:
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub words() {
|
||||
word @shared sbw = -30000
|
||||
word @shared pw = 30000
|
||||
const word cbw = -30000
|
||||
|
||||
txt.print("\nsigned words\n")
|
||||
txt.print("expected: ")
|
||||
txt.print_w(cbw)
|
||||
txt.spc()
|
||||
txt.print_bool(cbw>30000)
|
||||
txt.spc()
|
||||
txt.print_bool(cbw>=30000)
|
||||
txt.spc()
|
||||
txt.print_bool(cbw<30000)
|
||||
txt.spc()
|
||||
txt.print_bool(cbw<=30000)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" calc'd: ")
|
||||
txt.print_w(sbw)
|
||||
txt.spc()
|
||||
txt.print_bool(sbw>30000)
|
||||
txt.spc()
|
||||
txt.print_bool(sbw>=30000)
|
||||
txt.spc()
|
||||
txt.print_bool(sbw<30000)
|
||||
txt.spc()
|
||||
txt.print_bool(sbw<=30000)
|
||||
txt.nl()
|
||||
|
||||
txt.print("calc'd 2: ")
|
||||
txt.print_w(sbw)
|
||||
txt.spc()
|
||||
txt.print_bool(sbw>pw)
|
||||
txt.spc()
|
||||
txt.print_bool(sbw>=pw)
|
||||
txt.spc()
|
||||
txt.print_bool(sbw<pw)
|
||||
txt.spc()
|
||||
txt.print_bool(sbw<=pw)
|
||||
txt.nl()
|
||||
|
||||
txt.print(" if stmt: ")
|
||||
txt.print_w(sbw)
|
||||
txt.spc()
|
||||
if sbw>30000 txt.print("true ") else txt.print("false ")
|
||||
if sbw>=30000 txt.print("true ") else txt.print("false ")
|
||||
if sbw<30000 txt.print("true ") else txt.print("false ")
|
||||
if sbw<=30000 txt.print("true ") else txt.print("false ")
|
||||
txt.nl()
|
||||
|
||||
txt.print(" ifstmt2: ")
|
||||
txt.print_w(sbw)
|
||||
txt.spc()
|
||||
if sbw>pw txt.print("true ") else txt.print("false ")
|
||||
if sbw>=pw txt.print("true ") else txt.print("false ")
|
||||
if sbw<pw txt.print("true ") else txt.print("false ")
|
||||
if sbw<=pw txt.print("true ") else txt.print("false ")
|
||||
txt.nl()
|
||||
cx16.r0++
|
||||
}
|
||||
}
|
||||
|
||||
some_block {
|
||||
uword buffer = memory("arena", 2000, 0)
|
||||
}
|
||||
|
||||
|
||||
other_block {
|
||||
sub redherring (uword buffer) {
|
||||
%ir {{
|
||||
loadm.w r99000,other_block.redherring.buffer
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
@ -3,4 +3,4 @@ org.gradle.console=rich
|
||||
org.gradle.parallel=true
|
||||
org.gradle.daemon=true
|
||||
kotlin.code.style=official
|
||||
version=11.4-SNAPSHOT
|
||||
version=11.4
|
||||
|
@ -52,6 +52,10 @@ class IRSymbolTable {
|
||||
fun validate() {
|
||||
require(table.all { it.key == it.value.name })
|
||||
}
|
||||
|
||||
fun removeIfExists(labelSymbol: String) {
|
||||
table.remove(labelSymbol)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
id("antlr")
|
||||
id("java")
|
||||
@ -16,7 +14,7 @@ configurations.all {
|
||||
|
||||
tasks.generateGrammarSource {
|
||||
outputDirectory = file("src/prog8/parser")
|
||||
arguments.addAll(listOf("-no-listener", "-no-visitor"))
|
||||
arguments.addAll(listOf("-no-listener", "-visitor"))
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
|
Reference in New Issue
Block a user