Compare commits

...
10 Commits
19 changed files with 307 additions and 86 deletions

No files matched your search

+1
View File
@@ -240,6 +240,7 @@ When writing inline assembly (`%asm {{ }}` blocks or `asmsub` routines), load th
- **No bare `{ }` blocks** like C/Java
- Indentation: 4 spaces for .p8 and .asm files (no tabs)
- Character encoding: 6502 targets use PETSCII by default (call `txt.lowercase()` at start for lowercase). Virtual target uses ISO (`%encoding iso` + `txt.iso()`)
- **Test programs for CBM targets must only use lowercase text in string literals** (the C64/C128/PET32/CX16 targets all use PETSCII). In PETSCII, uppercase letters A-Z are the *graphics* symbols, so any uppercase text you put in a string literal is rendered as unpredictable graphics characters instead of readable letters. Use only lowercase (a-z) for any printed test output, labels, or expected-match strings on these targets; reserve uppercase for intentional PETSCII graphics. The `virtual` target (ISO) is unaffected and may use either case.
- Array size inferred from initializer: `str[] types = ["a", "b", "c"]`
- Enums: `Enum::Value` syntax (double colon), declared inside a block. They are syntactic sugar for a list of `const` declarations, not a type. Use enums for related values, `const` for standalone
- Avoid `globals.XXXX` — move constants closer to where they're used
@@ -224,6 +224,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
}
"or=" -> {
val shortcutLabel = codeGen.createLabelName()
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
addInstr(inplaceInstrs, IRInstruction(Opcode.CMPI, targetDt, reg1 = oldvalueReg, immediate = 0), null)
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null)
val valueTr = exprGen.translateExpression(value)
inplaceInstrs += valueTr.chunks
@@ -232,6 +234,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
}
"and=" -> {
val shortcutLabel = codeGen.createLabelName()
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
addInstr(inplaceInstrs, IRInstruction(Opcode.CMPI, targetDt, reg1 = oldvalueReg, immediate = 0), null)
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null)
val valueTr = exprGen.translateExpression(value)
inplaceInstrs += valueTr.chunks
@@ -385,7 +389,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
val arrayVariableName = array.variable!!.name
val skipCarryLabel = codeGen.createLabelName()
if(constIndex!=null) {
val negLsbReg = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.NEGM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex), null)
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = negLsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex), null)
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = negLsbReg, immediate = 0), null)
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = skipCarryLabel), null)
addInstr(result, IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex), null)
addInstr(result, IRInstruction(Opcode.NEGM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex), skipCarryLabel)
@@ -1029,6 +1037,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, address = constAddress.toAddress())
else
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, labelSymbol = symbol)
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
it += IRInstruction(Opcode.CMPI, vmDt, reg1=inplaceReg, immediate = 0)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel)
}
addToResult(result, tr, tr.resultReg, -1)
@@ -1289,6 +1299,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, address = constAddress.toAddress())
else
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, labelSymbol = symbol)
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
it += IRInstruction(Opcode.CMPI, vmDt, reg1=inplaceReg, immediate = 0)
it += IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel)
}
addToResult(result, tr, tr.resultReg, -1)
@@ -1716,6 +1728,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
val lsbReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex)
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = lsbReg, immediate = 0)
it += IRInstruction(Opcode.BSTNE, labelSymbol = skip)
it += IRInstruction(Opcode.DECM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex)
}
@@ -1968,8 +1982,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
if(constValue==1) {
val skip = codeGen.createLabelName()
val lsbReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex)
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex)
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = lsbReg, immediate = 0)
it += IRInstruction(Opcode.BSTNE, labelSymbol = skip)
it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex)
}
@@ -300,12 +300,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
val needsExplicitCmpi = !codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps
when (sourceDt.base) {
BaseDataType.BYTE -> {
val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=tr.resultReg)
if(needsExplicitCmpi)
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=compareReg, immediate = 0)
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=tr.resultReg)
}
@@ -317,6 +320,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val compareReg = codeGen.registers.next(IRDataType.WORD)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=tr.resultReg)
if(needsExplicitCmpi)
it += IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=compareReg, immediate = 0)
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=tr.resultReg)
}
@@ -328,6 +333,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val compareReg = codeGen.registers.next(IRDataType.LONG)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1=compareReg, reg2=tr.resultReg)
if(needsExplicitCmpi)
it += IRInstruction(Opcode.CMPI, IRDataType.LONG, reg1=compareReg, immediate = 0)
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.LONG, reg1=tr.resultReg)
}
@@ -304,7 +304,6 @@ class Inliner(private val program: Program, private val options: CompilationOpti
fun inlineFunctionBody(toInline: Return): Iterable<AstModification> {
// call site is an expression, so we have to have a Return here in the inlined sub to provide the values
return if (toInline.values.size == 1 && functionCallExpr !== toInline.values[0]) {
logInlining(sub, functionCallExpr)
sub.hasBeenInlined = true
val substitutedReturn = substituteParameters(sub, functionCallExpr, toInline.values[0]) as Expression
listOf(AstReplaceNode(functionCallExpr, substitutedReturn, parent))
@@ -332,13 +331,11 @@ class Inliner(private val program: Program, private val options: CompilationOpti
if (functionCalls.isEmpty()) {
// No function calls in the return values - the void call has no side effects and can be removed.
logInlining(sub, origNode)
sub.hasBeenInlined = true
return listOf(AstRemove(origNode as Statement, parent as IStatementContainer))
}
// There are function calls in the return values - convert each to a void statement.
logInlining(sub, origNode)
sub.hasBeenInlined = true
val voidStatements: MutableList<Statement> = functionCalls.map { fcall ->
@@ -359,7 +356,6 @@ class Inliner(private val program: Program, private val options: CompilationOpti
val inlinedStatement = substituteParameters(sub, origNode as IFunctionCall, toInline) as Statement
return if (origNode !== toInline) {
logInlining(sub, origNode)
sub.hasBeenInlined = true
listOf(AstReplaceNode(origNode, inlinedStatement, parent))
} else
@@ -391,13 +387,6 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
private fun logInlining(sub: Subroutine, call: Node) {
// TODO remove this logging once we are sure everything works good
if (sub.parameters.isNotEmpty() && !options.quiet) {
println(">>> INLINER: inlining '${sub.name}' at ${call.position} (subroutine at ${sub.position})")
}
}
private fun isSimpleReturnExpression(expr: Expression): Boolean {
return when (expr) {
is NumericLiteral -> true
+19 -4
View File
@@ -349,16 +349,28 @@ hline_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %1111
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled.
; Overdraws horizontal lines unfortunately.
if radius==0
return
ubyte @zp yy = 0
word decisionOver2 = (1 as word)-radius
ubyte pendingRadius
ubyte pendingWidth
bool hasPending = false
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
if hasPending and pendingRadius != radius {
if pendingRadius != pendingWidth {
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*2+1)
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*2+1)
}
hasPending = false
}
if not hasPending {
pendingRadius = radius
hasPending = true
}
pendingWidth = yy
yy++
if decisionOver2>=0 {
radius--
@@ -367,6 +379,10 @@ hline_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %1111
decisionOver2 += yy*$0002
decisionOver2++
}
if hasPending and pendingRadius != pendingWidth {
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*2+1)
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*2+1)
}
}
@@ -446,4 +462,3 @@ _y_lookup_hi .byte >_plot_y_values
}
+38 -4
View File
@@ -439,12 +439,25 @@ gfx_hires {
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
ubyte pendingRadius
ubyte pendingWidth
bool hasPending = false
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
if hasPending and pendingRadius != radius {
if pendingRadius != pendingWidth {
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
}
hasPending = false
}
if not hasPending {
pendingRadius = radius
hasPending = true
}
pendingWidth = yy
yy++
if decisionOver2>=0 {
radius--
@@ -453,6 +466,10 @@ gfx_hires {
decisionOver2 += yy*$0002
decisionOver2++
}
if hasPending and pendingRadius != pendingWidth {
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
}
}
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
@@ -462,12 +479,25 @@ gfx_hires {
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
ubyte pendingRadius
ubyte pendingWidth
bool hasPending = false
while radius>=yy {
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
if hasPending and pendingRadius != radius {
if pendingRadius != pendingWidth {
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
}
hasPending = false
}
if not hasPending {
pendingRadius = radius
hasPending = true
}
pendingWidth = yy
yy++
if decisionOver2>=0 {
radius--
@@ -476,6 +506,10 @@ gfx_hires {
decisionOver2 += yy*$0002
decisionOver2++
}
if hasPending and pendingRadius != pendingWidth {
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
}
}
sub plot(uword @zp xx, uword @zp yy, ubyte @zp color) {
+46 -8
View File
@@ -501,11 +501,24 @@ gfx_lores {
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
ubyte pendingRadius
ubyte pendingWidth
bool hasPending = false
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
if hasPending and pendingRadius != radius {
if pendingRadius != pendingWidth {
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
}
hasPending = false
}
if not hasPending {
pendingRadius = radius
hasPending = true
}
pendingWidth = yy
yy++
if decisionOver2>=0 {
radius--
@@ -514,6 +527,10 @@ gfx_lores {
decisionOver2 += yy*$0002
decisionOver2++
}
if hasPending and pendingRadius != pendingWidth {
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
}
}
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
@@ -523,6 +540,9 @@ gfx_lores {
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
ubyte pendingRadius
ubyte pendingWidth
bool hasPending = false
while radius>=yy {
uword liney = ycenter+yy
@@ -532,12 +552,22 @@ gfx_lores {
if msb(liney)==0
safe_horizontal_line(xcenter-radius, lsb(liney), radius*$0002+1, color)
liney = ycenter+radius
if msb(liney)==0
safe_horizontal_line(xcenter-yy, lsb(liney), yy*$0002+1, color)
liney = ycenter-radius
if msb(liney)==0
safe_horizontal_line(xcenter-yy, lsb(liney), yy*$0002+1, color)
if hasPending and pendingRadius != radius {
if pendingRadius != pendingWidth {
liney = ycenter+pendingRadius
if msb(liney)==0
safe_horizontal_line(xcenter-pendingWidth, lsb(liney), pendingWidth*$0002+1, color)
liney = ycenter-pendingRadius
if msb(liney)==0
safe_horizontal_line(xcenter-pendingWidth, lsb(liney), pendingWidth*$0002+1, color)
}
hasPending = false
}
if not hasPending {
pendingRadius = radius
hasPending = true
}
pendingWidth = yy
yy++
if decisionOver2>=0 {
@@ -547,6 +577,14 @@ gfx_lores {
decisionOver2 += yy*$0002
decisionOver2++
}
if hasPending and pendingRadius != pendingWidth {
uword flushLiney = ycenter+pendingRadius
if msb(flushLiney)==0
safe_horizontal_line(xcenter-pendingWidth, lsb(flushLiney), pendingWidth*$0002+1, color)
flushLiney = ycenter-pendingRadius
if msb(flushLiney)==0
safe_horizontal_line(xcenter-pendingWidth, lsb(flushLiney), pendingWidth*$0002+1, color)
}
}
asmsub plot(uword x @AX, ubyte y @Y, ubyte color @R0) {
+38 -6
View File
@@ -604,17 +604,29 @@ drawmode: ora cx16.r15L
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
; Midpoint algorithm, filled
; Note: has problems with INVERT draw mode because of horizontal span overdrawing. Horizontal lines may occur.
if radius==0
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
ubyte pendingRadius
ubyte pendingWidth
bool hasPending = false
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
if hasPending and pendingRadius != radius {
if pendingRadius != pendingWidth {
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
}
hasPending = false
}
if not hasPending {
pendingRadius = radius
hasPending = true
}
pendingWidth = yy
yy++
if decisionOver2>=0 {
radius--
@@ -623,22 +635,38 @@ drawmode: ora cx16.r15L
decisionOver2 += yy*$0002
decisionOver2++
}
if hasPending and pendingRadius != pendingWidth {
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
}
}
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
; Does bounds checking and clipping.
; Midpoint algorithm, filled
; Note: has problems with INVERT draw mode because of horizontal span overdrawing. Horizontal lines may occur.
if radius==0
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
ubyte pendingRadius
ubyte pendingWidth
bool hasPending = false
while radius>=yy {
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
if hasPending and pendingRadius != radius {
if pendingRadius != pendingWidth {
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
}
hasPending = false
}
if not hasPending {
pendingRadius = radius
hasPending = true
}
pendingWidth = yy
yy++
if decisionOver2>=0 {
radius--
@@ -647,6 +675,10 @@ drawmode: ora cx16.r15L
decisionOver2 += yy*$0002
decisionOver2++
}
if hasPending and pendingRadius != pendingWidth {
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
}
}
sub plot(uword @zp xx, uword @zp yy, bool @zp draw) {
+38 -4
View File
@@ -304,11 +304,24 @@ monogfx {
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
ubyte pendingRadius
ubyte pendingWidth
bool hasPending = false
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
if hasPending and pendingRadius != radius {
if pendingRadius != pendingWidth {
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
}
hasPending = false
}
if not hasPending {
pendingRadius = radius
hasPending = true
}
pendingWidth = yy
yy++
if decisionOver2>=0 {
radius--
@@ -317,6 +330,10 @@ monogfx {
decisionOver2 += yy*$0002
decisionOver2++
}
if hasPending and pendingRadius != pendingWidth {
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
}
}
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
@@ -326,11 +343,24 @@ monogfx {
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
ubyte pendingRadius
ubyte pendingWidth
bool hasPending = false
while radius>=yy {
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
if hasPending and pendingRadius != radius {
if pendingRadius != pendingWidth {
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
}
hasPending = false
}
if not hasPending {
pendingRadius = radius
hasPending = true
}
pendingWidth = yy
yy++
if decisionOver2>=0 {
radius--
@@ -339,6 +369,10 @@ monogfx {
decisionOver2 += yy*$0002
decisionOver2++
}
if hasPending and pendingRadius != pendingWidth {
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
}
}
sub plot(uword @zp xx, uword @zp yy, bool @zp draw) {
+12 -7
View File
@@ -2,6 +2,7 @@ package prog8
import kotlinx.cli.*
import prog8.ast.AstException
import prog8.code.core.ErrorsReportedException
import prog8.code.core.Position
import prog8.code.source.ImportFileSystem
import prog8.code.source.ImportFileSystem.expandTilde
@@ -836,6 +837,11 @@ private fun communicateWithDaemon(channel: SocketChannel, compilerArgs: Compiler
return response
}
System.out.print(response.stdout)
System.out.flush()
System.err.print(response.stderr)
System.err.flush()
val txtcolors = if(plainText) ErrorReporter.PlainText else ErrorReporter.AnsiColors
val reporter = ErrorReporter(txtcolors)
for (error in response.errors) {
@@ -846,13 +852,12 @@ private fun communicateWithDaemon(channel: SocketChannel, compilerArgs: Compiler
"INFO" -> reporter.info(error.message, pos)
}
}
reporter.report()
System.out.print(response.stdout)
System.out.flush()
System.err.print(response.stderr)
System.out.flush()
System.err.flush()
try {
reporter.report()
} catch (x: ErrorsReportedException) {
if(!x.message.isNullOrEmpty())
reporter.printSingleError(x.message!!)
}
return response
} finally {
@@ -175,17 +175,20 @@ class ModuleImporter(private val program: Program,
return result.mapError { NoSuchFileException(File(name)) }
}
private fun getModuleFromFile(name: String, importingModule: Module?): Pair<Result<SourceCode, NoSuchFileException>, List<Path>> {
internal fun getModuleFromFile(name: String, importingModule: Module?): Pair<Result<SourceCode, NoSuchFileException>, List<Path>> {
val fileName = "$name.p8"
val requestedBy = importingModule?.name ?: "~implicit~"
val cwd = Path("").absolute()
val normalLocations =
if (importingModule == null) {
(sourcePaths + listOf(Path("").absolute())).distinct()
} else {
(sourcePaths + listOf(cwd)).distinct()
} else if (SourceCode.isRegularFilesystemPath(importingModule.position.file)) {
val pathFromImportingModule = (Path(importingModule.position.file).parent ?: Path("")).sanitize()
val cwd = Path("").absolute()
(sourcePaths + listOf(pathFromImportingModule, cwd)).distinct()
} else {
// library/string resources have no neighboring directory on disk
(sourcePaths + listOf(cwd)).distinct()
}
val libraryPathsSet = libraryPaths.toSet()
@@ -151,17 +151,47 @@ class AstPreprocessor(val program: Program,
if(decl.names.size>1) {
// we need to handle multi-decl here too, the desugarer maybe has not processed it here yet...
if(decl.value!=null) {
decl.names.forEach { name ->
val target = AssignTarget(
IdentifierReference(listOf(name), decl.position),
null,
null,
null,
false,
position = decl.position
// only use a multi-value assignment if the initializer is a function call with multiple
// return values, so that the call is only performed once. Otherwise (e.g. a constant
// initializer) we have to fall back to individual assignments.
val fcall = decl.value as? IFunctionCall
val fcallTarget = fcall?.target?.targetSubroutine()
val isBuiltinMultiReturn = fcall?.target?.let { target ->
val name = target.nameInSource.singleOrNull()
name != null && name in program.builtinFunctions.names &&
program.builtinFunctions.returnTypes(name).size > 1
} ?: false
if(fcallTarget!=null || isBuiltinMultiReturn) {
val variables = decl.names.map { name ->
AssignTarget(
IdentifierReference(listOf(name), decl.position),
null,
null,
null,
false,
position = decl.position
)
}
val assign = Assignment(
AssignTarget(null, null, null, variables, false, position = decl.position),
decl.value!!,
AssignmentOrigin.VARINIT,
decl.position
)
val assign = Assignment(target.copy(), decl.value!!.copy(), AssignmentOrigin.VARINIT, decl.position)
replacements.add(AstInsert.after(decl, assign, scope))
} else {
decl.names.forEach { name ->
val target = AssignTarget(
IdentifierReference(listOf(name), decl.position),
null,
null,
null,
false,
position = decl.position
)
val assign = Assignment(target, decl.value!!.copy(), AssignmentOrigin.VARINIT, decl.position)
replacements.add(AstInsert.after(decl, assign, scope))
}
}
replacements.add(AstRemove(decl, scope))
decl.value = null
+6 -6
View File
@@ -162,7 +162,7 @@ class TestCompilerOnExamplesC64: FunSpec({
ExampleSizes("simplemultiplexer", c64SizeOptimized=843, c64SizeUnoptimized=900),
ExampleSizes("sprites", c64SizeOptimized=963, c64SizeUnoptimized=1028),
ExampleSizes("starfield", c64SizeOptimized=743, c64SizeUnoptimized=758),
ExampleSizes("turtle-gfx", c64SizeOptimized=2701, c64SizeUnoptimized=2806),
ExampleSizes("turtle-gfx", c64SizeOptimized=2701, c64SizeUnoptimized=2812),
ExampleSizes("wizzine", c64SizeOptimized=1124, c64SizeUnoptimized=1224),
// boingball, cube3d, tehtriz are in BothC64andCx16
),
@@ -211,7 +211,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
ExampleSizes("fileselector/main", cx16SizeOptimized=480, cx16SizeUnoptimized=2314),
// fileselector/fselector and fileselector/namesorting don't compile (empty source files)
ExampleSizes("pointers/fountain-cx16", cx16SizeOptimized=1179, cx16SizeUnoptimized=1520),
ExampleSizes("amiga", cx16SizeOptimized=7631, cx16SizeUnoptimized=11628),
ExampleSizes("amiga", cx16SizeOptimized=7810, cx16SizeUnoptimized=11827),
ExampleSizes("audioroutines", cx16SizeOptimized=948, cx16SizeUnoptimized=1244),
ExampleSizes("automatons", cx16SizeOptimized=1037, cx16SizeUnoptimized=1342),
ExampleSizes("balloonflight", cx16SizeOptimized=3187, cx16SizeUnoptimized=4202),
@@ -220,7 +220,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
ExampleSizes("bubbleuniverse", cx16SizeOptimized=1367, cx16SizeUnoptimized=1883),
ExampleSizes("charfade", cx16SizeOptimized=1054, cx16SizeUnoptimized=1376),
ExampleSizes("charsets", cx16SizeOptimized=1125, cx16SizeUnoptimized=1417),
ExampleSizes("circles", cx16SizeOptimized=2377, cx16SizeUnoptimized=4495),
ExampleSizes("circles", cx16SizeOptimized=2535, cx16SizeUnoptimized=4689),
ExampleSizes("cobramk3-gfx", cx16SizeOptimized=7756, cx16SizeUnoptimized=10434),
ExampleSizes("colorbars", cx16SizeOptimized=1606, cx16SizeUnoptimized=1904),
ExampleSizes("cxlogo", cx16SizeOptimized=599, cx16SizeUnoptimized=895),
@@ -243,9 +243,9 @@ class TestCompilerOnExamplesCx16: FunSpec({
ExampleSizes("sortingbench", cx16SizeOptimized=2928, cx16SizeUnoptimized=3857),
ExampleSizes("spotlight", cx16SizeOptimized=908, cx16SizeUnoptimized=1364),
ExampleSizes("starszoom", cx16SizeOptimized=2399, cx16SizeUnoptimized=4820),
ExampleSizes("test_gfx_hires", cx16SizeOptimized=4760, cx16SizeUnoptimized=7444),
ExampleSizes("test_gfx_lores", cx16SizeOptimized=5820, cx16SizeUnoptimized=7004),
ExampleSizes("testmonogfx", cx16SizeOptimized=10255, cx16SizeUnoptimized=11631),
ExampleSizes("test_gfx_hires", cx16SizeOptimized=4966, cx16SizeUnoptimized=7650),
ExampleSizes("test_gfx_lores", cx16SizeOptimized=6019, cx16SizeUnoptimized=7182),
ExampleSizes("testmonogfx", cx16SizeOptimized=10468, cx16SizeUnoptimized=11847),
ExampleSizes("textspotlight", cx16SizeOptimized=3431, cx16SizeUnoptimized=3788),
),
listOf(false, true)
+19
View File
@@ -8,13 +8,16 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldBeIn
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.string.shouldStartWith
import prog8.ast.Program
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.PROG8_CONTAINER_MODULES
import prog8.code.core.IErrorReporter
import prog8.code.source.ImportFileSystem
import prog8.code.source.SourceCode
import prog8.compiler.ModuleImporter
import prog8.parser.MultipleParseErrors
import prog8.parser.Prog8Parser
import prog8tests.helpers.*
import kotlin.io.path.*
@@ -190,6 +193,22 @@ class TestModuleImporter: FunSpec({
}
}
context("ImportFromResourceModule") {
test("neighbor directory search is skipped for modules loaded from internal library resources") {
val importer = makeImporter(null)
val resourceModule = Prog8Parser.parseModule(ImportFileSystem.getResource("/prog8lib/cx16/textio.p8"))
resourceModule.position.file shouldStartWith "library:"
val (result, searched) = importer.getModuleFromFile("syslib", resourceModule)
withClue("syslib only exists as an internal library resource, not on the filesystem") {
result.getErrorOrElse { error("expected a filesystem search failure") }.file.name shouldBe "syslib"
}
withClue("no filesystem search location may be derived from the library: origin: $searched") {
searched.none { it.toString().contains("library:") } shouldBe true
}
}
}
context("ImportLibraryModule") {
context("WithInvalidName") {
test("testWithNonExistingName") {
+1 -2
View File
@@ -250,7 +250,7 @@ Major breaking changes that require code modifications when upgrading:
- **Fuzzy library search** — ``-libsearch`` now automatically attempts a fuzzy search if no exact matches are found.
**v12.3** — Not released yet
**v12.3** — August 2026
- Laying groundwork for new code generation targets. Still highly experimental.
- **Module search order rework** — replaced alphabetical search with a deterministic, prioritized list; filesystem now searched before internal libraries.
- **New experimental 6502 code generator** based on the IR, enabled with ``-newcodegen`` (do not use for production code yet).
@@ -259,7 +259,6 @@ Major breaking changes that require code modifications when upgrading:
- **``@bank`` accepts a subroutine name** — for dynamic / overlay banking with a bank manager
- **New library routines** — ``floats.mod()``; ``sys.MAX_LONG`` / ``sys.MIN_LONG``; PET32 ``petsnd`` playback functions, ``REST`` constant, ``set_gap()``, enharmonic flat note symbols; PET32 ``sys.set_irq()`` / ``sys.restore_irq()``; serial module: configurable baud rate.
- **Breaking changes** — ``-nostdlib`` removed; ``bankmanager`` renamed to ``bankselector``; ``swap`` is now a statement.
- **Search Path Comparison**:
*This document summarizes major and minor releases. Bugfix releases (e.g., v12.0.1, v12.1.1) are omitted for brevity.*
+14 -19
View File
@@ -1,26 +1,21 @@
%import textio
%zeropage basicsafe
main {
ubyte @shared counter
bool @shared flag
sub start() {
; Issue 4: @(...) is always an UNSIGNED byte read. The optimizer rewrite
; @(&x) -> x (ConstantFoldingOptimizer / MemoryOptimizers) must not turn the
; unsigned memory read into the signed variable, which would sign-extend on widen.
; Reading -1 from memory (0xFF) must give 255, not 65535.
byte @shared sv = -1 ; memory holds 0xFF
word a = @(&sv) ; zero-extends the unsigned byte to 255
txt.print("mem-read signed byte -1: ")
txt.print_w(a)
txt.print(" (raw 0xFF; zero-extends to 255)\n")
if flag {
ubyte a,b = multi() ; compiler error when inside if block, OK if in subroutine directly
txt.print_ub(a)
txt.spc()
txt.print_ub(b)
txt.nl()
}
}
; Contrast: an UNSIGNED byte read via @(&x) is correctly zero-extended.
ubyte @shared uv = 255 ; memory also holds 0xFF
word b = @(&uv) ; zero-extends to 255
txt.print("mem-read unsigned byte 255: ")
txt.print_w(b)
txt.print(" (raw 0xFF; zero-extends to 255)\n")
txt.print("done\n")
;sys.poweroff_system()
sub multi() -> ubyte, ubyte {
counter++
return 11,22
}
}
+1 -1
View File
@@ -4,4 +4,4 @@ org.gradle.parallel=true
org.gradle.daemon=true
org.gradle.configuration-cache=false
kotlin.code.style=official
version=12.3
version=12.3.1
@@ -1116,6 +1116,8 @@ data class IRInstruction(
return IRDataType.BYTE
if(opcode==Opcode.SQRT)
return IRDataType.WORD
if(opcode==Opcode.STOREZX)
return IRDataType.BYTE
}
if(opcode in setOf(Opcode.JUMPI, Opcode.CALLI, Opcode.STOREZI, Opcode.LSIGW, Opcode.MSIGW))
return IRDataType.WORD
+1 -1
View File
@@ -17,7 +17,7 @@ fun DataType.irTypeString(length: UInt?): String {
BaseDataType.WORD -> "word"
BaseDataType.LONG -> "long"
BaseDataType.FLOAT -> "float"
BaseDataType.STR -> "ubyte[$lengthStr]" // here string doesn't exist as a seperate datatype anymore
BaseDataType.STR -> "ubyte[$lengthStr]" // here string doesn't exist as a separate datatype anymore
BaseDataType.POINTER -> "uword"
BaseDataType.ARRAY_POINTER -> "uword"
BaseDataType.STRUCT_INSTANCE -> {