Compare commits

...

11 Commits

40 changed files with 1404 additions and 1832 deletions

1
.idea/misc.xml generated
View File

@ -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>

View File

@ -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

View File

@ -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) {

View File

@ -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
+""")

View File

@ -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 {

View File

@ -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)
)
}

View File

@ -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
)
}

View File

@ -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

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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).

View 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)
}
}
}
}

View File

@ -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 {

View File

@ -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 {{

View File

@ -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) {

View File

@ -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)
}
}
}

View File

@ -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())

View File

@ -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)

View File

@ -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

View File

@ -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
}
})

View File

@ -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]

View File

@ -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"
}
})

View File

@ -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)

View File

@ -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())
}

View 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?)
}

View File

@ -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

View File

@ -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 }

View File

@ -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?) {

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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
}}
}
}

View File

@ -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

View File

@ -52,6 +52,10 @@ class IRSymbolTable {
fun validate() {
require(table.all { it.key == it.value.name })
}
fun removeIfExists(labelSymbol: String) {
table.remove(labelSymbol)
}
}

View File

@ -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 {