diff --git a/compiler/res/prog8lib/c64utils.p8 b/compiler/res/prog8lib/c64utils.p8 index 4b0e11359..0c4a941ad 100644 --- a/compiler/res/prog8lib/c64utils.p8 +++ b/compiler/res/prog8lib/c64utils.p8 @@ -11,10 +11,6 @@ c64utils { - const uword ESTACK_LO = $ce00 - const uword ESTACK_HI = $cf00 - - ; ----- number conversions to decimal strings asmsub ubyte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X { @@ -859,7 +855,7 @@ asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) { }} } -asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) { +asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) { ; ---- print the uword in A/Y in hexadecimal form (4 digits) ; (if Carry is set, a radix prefix '$' is printed as well) %asm {{ diff --git a/compiler/res/prog8lib/cx16lib.p8 b/compiler/res/prog8lib/cx16lib.p8 new file mode 100644 index 000000000..c636eb8d0 --- /dev/null +++ b/compiler/res/prog8lib/cx16lib.p8 @@ -0,0 +1,79 @@ +; Prog8 definitions for the CommanderX16 +; Including memory registers, I/O registers, Basic and Kernal subroutines. +; +; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 +; +; indent format: TABS, size=8 + + +cx16 { + +; ---- C64 kernal routines ---- + +romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip +romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ) +romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen +romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors +romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table +romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag +romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN +romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK +romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer +romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer +romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard +romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus +romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus +romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus +romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK +romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN +romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN +romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK +romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word +romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters +romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters +romsub $FFC0 = OPEN() clobbers(A,X,Y) ; (via 794 ($31A)) open a logical file +romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file +romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) ; (via 798 ($31E)) define an input channel +romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel +romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices +romsub $FFCF = CHRIN() clobbers(Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read. +romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character +romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device +romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device +romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock +romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock +romsub $FFE1 = STOP() clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc ; (via 808 ($328)) check the STOP key +romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @ A ; (via 810 ($32A)) get a character +romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files +romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock +romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns +romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X. +romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices + + +; ---- end of kernal routines ---- + +asmsub init_system() { + ; Initializes the machine to a sane starting state. + ; Called automatically by the loader program logic. + %asm {{ + sei + cld + ;lda #%00101111 + ;sta $00 + ;lda #%00100111 + ;sta $01 + jsr cx16.IOINIT + jsr cx16.RESTOR + jsr cx16.CINT + lda #0 + tax + tay + clc + clv + cli + rts + }} +} + +} diff --git a/compiler/res/prog8lib/cx16utils.p8 b/compiler/res/prog8lib/cx16utils.p8 new file mode 100644 index 000000000..72716f5c0 --- /dev/null +++ b/compiler/res/prog8lib/cx16utils.p8 @@ -0,0 +1,604 @@ +; Prog8 definitions for the CommanderX16 +; These are the utility subroutines. +; +; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 +; +; indent format: TABS, size=8 + + +%import cx16lib + + +cx16utils { + +; ----- number conversions to decimal strings + +asmsub ubyte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X { + ; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X) + %asm {{ + ldy #uword2decimal.ASCII_0_OFFSET + bne uword2decimal.hex_try200 + rts + }} +} + +asmsub uword2decimal (uword value @ AY) -> ubyte @Y, ubyte @A, ubyte @X { + ; ---- convert 16 bit uword in A/Y to decimal + ; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes + ; (these are terminated by a zero byte so they can be easily printed) + ; also returns Y = 100's, A = 10's, X = 1's + + %asm {{ + +;Convert 16 bit Hex to Decimal (0-65535) Rev 2 +;By Omegamatrix Further optimizations by tepples +; routine from http://forums.nesdev.com/viewtopic.php?f=2&t=11341&start=15 + +;HexToDec99 +; start in A +; end with A = 10's, decOnes (also in X) + +;HexToDec255 +; start in A +; end with Y = 100's, A = 10's, decOnes (also in X) + +;HexToDec999 +; start with A = high byte, Y = low byte +; end with Y = 100's, A = 10's, decOnes (also in X) +; requires 1 extra temp register on top of decOnes, could combine +; these two if HexToDec65535 was eliminated... + +;HexToDec65535 +; start with A/Y (low/high) as 16 bit value +; end with decTenThousand, decThousand, Y = 100's, A = 10's, decOnes (also in X) +; (irmen: I store Y and A in decHundreds and decTens too, so all of it can be easily printed) + + +ASCII_0_OFFSET = $30 +temp = P8ZP_SCRATCH_B1 ; byte in zeropage +hexHigh = P8ZP_SCRATCH_W1 ; byte in zeropage +hexLow = P8ZP_SCRATCH_W1+1 ; byte in zeropage + + +HexToDec65535; SUBROUTINE + sty hexHigh ;3 @9 + sta hexLow ;3 @12 + tya + tax ;2 @14 + lsr a ;2 @16 + lsr a ;2 @18 integer divide 1024 (result 0-63) + + cpx #$A7 ;2 @20 account for overflow of multiplying 24 from 43,000 ($A7F8) onward, + adc #1 ;2 @22 we can just round it to $A700, and the divide by 1024 is fine... + + ;at this point we have a number 1-65 that we have to times by 24, + ;add to original sum, and Mod 1024 to get a remainder 0-999 + + + sta temp ;3 @25 + asl a ;2 @27 + adc temp ;3 @30 x3 + tay ;2 @32 + lsr a ;2 @34 + lsr a ;2 @36 + lsr a ;2 @38 + lsr a ;2 @40 + lsr a ;2 @42 + tax ;2 @44 + tya ;2 @46 + asl a ;2 @48 + asl a ;2 @50 + asl a ;2 @52 + clc ;2 @54 + adc hexLow ;3 @57 + sta hexLow ;3 @60 + txa ;2 @62 + adc hexHigh ;3 @65 + sta hexHigh ;3 @68 + ror a ;2 @70 + lsr a ;2 @72 + tay ;2 @74 integer divide 1,000 (result 0-65) + + lsr a ;2 @76 split the 1,000 and 10,000 digit + tax ;2 @78 + lda ShiftedBcdTab,x ;4 @82 + tax ;2 @84 + rol a ;2 @86 + and #$0F ;2 @88 + ora #ASCII_0_OFFSET + sta decThousands ;3 @91 + txa ;2 @93 + lsr a ;2 @95 + lsr a ;2 @97 + lsr a ;2 @99 + ora #ASCII_0_OFFSET + sta decTenThousands ;3 @102 + + lda hexLow ;3 @105 + cpy temp ;3 @108 + bmi _doSubtract ;2³ @110/111 + beq _useZero ;2³ @112/113 + adc #23 + 24 ;2 @114 +_doSubtract + sbc #23 ;2 @116 + sta hexLow ;3 @119 +_useZero + lda hexHigh ;3 @122 + sbc #0 ;2 @124 + +Start100s + and #$03 ;2 @126 + tax ;2 @128 0,1,2,3 + cmp #2 ;2 @130 + rol a ;2 @132 0,2,5,7 + ora #ASCII_0_OFFSET + tay ;2 @134 Y = Hundreds digit + + lda hexLow ;3 @137 + adc Mod100Tab,x ;4 @141 adding remainder of 256, 512, and 256+512 (all mod 100) + bcs hex_doSub200 ;2³ @143/144 + +hex_try200 + cmp #200 ;2 @145 + bcc hex_try100 ;2³ @147/148 +hex_doSub200 + iny ;2 @149 + iny ;2 @151 + sbc #200 ;2 @153 +hex_try100 + cmp #100 ;2 @155 + bcc HexToDec99 ;2³ @157/158 + iny ;2 @159 + sbc #100 ;2 @161 + +HexToDec99; SUBROUTINE + lsr a ;2 @163 + tax ;2 @165 + lda ShiftedBcdTab,x ;4 @169 + tax ;2 @171 + rol a ;2 @173 + and #$0F ;2 @175 + ora #ASCII_0_OFFSET + sta decOnes ;3 @178 + txa ;2 @180 + lsr a ;2 @182 + lsr a ;2 @184 + lsr a ;2 @186 + ora #ASCII_0_OFFSET + + ; irmen: load X with ones, and store Y and A too, for easy printing afterwards + sty decHundreds + sta decTens + ldx decOnes + rts ;6 @192 Y=hundreds, A = tens digit, X=ones digit + + +HexToDec999; SUBROUTINE + sty hexLow ;3 @9 + jmp Start100s ;3 @12 + +Mod100Tab + .byte 0,56,12,56+12 + +ShiftedBcdTab + .byte $00,$01,$02,$03,$04,$08,$09,$0A,$0B,$0C + .byte $10,$11,$12,$13,$14,$18,$19,$1A,$1B,$1C + .byte $20,$21,$22,$23,$24,$28,$29,$2A,$2B,$2C + .byte $30,$31,$32,$33,$34,$38,$39,$3A,$3B,$3C + .byte $40,$41,$42,$43,$44,$48,$49,$4A,$4B,$4C + +decTenThousands .byte 0 +decThousands .byte 0 +decHundreds .byte 0 +decTens .byte 0 +decOnes .byte 0 + .byte 0 ; zero-terminate the decimal output string + + }} +} + + +; ----- utility functions ---- + + +asmsub byte2decimal (byte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X { + ; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X) + ; note: if the number is negative, you have to deal with the '-' yourself! + %asm {{ + cmp #0 + bpl + + eor #255 + clc + adc #1 ++ jmp ubyte2decimal + }} +} + +asmsub ubyte2hex (ubyte value @ A) -> ubyte @ A, ubyte @ Y { + ; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y) + %asm {{ + stx P8ZP_SCRATCH_REG_X + pha + and #$0f + tax + ldy _hex_digits,x + pla + lsr a + lsr a + lsr a + lsr a + tax + lda _hex_digits,x + ldx P8ZP_SCRATCH_REG_X + rts + +_hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well + }} +} + +asmsub uword2hex (uword value @ AY) clobbers(A,Y) { + ; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated) + %asm {{ + sta P8ZP_SCRATCH_REG + tya + jsr ubyte2hex + sta output + sty output+1 + lda P8ZP_SCRATCH_REG + jsr ubyte2hex + sta output+2 + sty output+3 + rts +output .text "0000", $00 ; 0-terminated output buffer (to make printing easier) + }} +} + +asmsub str2uword(str string @ AY) -> uword @ AY { + ; -- returns the unsigned word value of the string number argument in AY + ; the number may NOT be preceded by a + sign and may NOT contain spaces + ; (any non-digit character will terminate the number string that is parsed) + %asm {{ +_result = P8ZP_SCRATCH_W2 + sta _mod+1 + sty _mod+2 + ldy #0 + sty _result + sty _result+1 +_mod lda $ffff,y ; modified + sec + sbc #48 + bpl + +_done ; return result + lda _result + ldy _result+1 + rts ++ cmp #10 + bcs _done + ; add digit to result + pha + jsr _result_times_10 + pla + clc + adc _result + sta _result + bcc + + inc _result+1 ++ iny + bne _mod + ; never reached + +_result_times_10 ; (W*4 + W)*2 + lda _result+1 + sta P8ZP_SCRATCH_REG + lda _result + asl a + rol P8ZP_SCRATCH_REG + asl a + rol P8ZP_SCRATCH_REG + clc + adc _result + sta _result + lda P8ZP_SCRATCH_REG + adc _result+1 + asl _result + rol a + sta _result+1 + rts + }} +} + +asmsub str2word(str string @ AY) -> word @ AY { + ; -- returns the signed word value of the string number argument in AY + ; the number may be preceded by a + or - sign but may NOT contain spaces + ; (any non-digit character will terminate the number string that is parsed) + %asm {{ +_result = P8ZP_SCRATCH_W2 + sta P8ZP_SCRATCH_W1 + sty P8ZP_SCRATCH_W1+1 + ldy #0 + sty _result + sty _result+1 + sty _negative + lda (P8ZP_SCRATCH_W1),y + cmp #'+' + bne + + iny ++ cmp #'-' + bne _parse + inc _negative + iny +_parse lda (P8ZP_SCRATCH_W1),y + sec + sbc #48 + bpl _digit +_done ; return result + lda _negative + beq + + sec + lda #0 + sbc _result + sta _result + lda #0 + sbc _result+1 + sta _result+1 ++ lda _result + ldy _result+1 + rts +_digit cmp #10 + bcs _done + ; add digit to result + pha + jsr str2uword._result_times_10 + pla + clc + adc _result + sta _result + bcc + + inc _result+1 ++ iny + bne _parse + ; never reached +_negative .byte 0 + }} +} + + +} ; ------ end of block cx16utils + + + + +screen { + + ; ---- this block contains (character) Screen and text I/O related functions ---- + + +asmsub clear_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) { + ; ---- clear the character screen with the given fill character and character color. + ; (assumes screen and color matrix are at their default addresses) + + %asm {{ + brk ; TODO + }} + +} + +asmsub print (str text @ AY) clobbers(A,Y) { + ; ---- print null terminated string from A/Y + ; note: the compiler contains an optimization that will replace + ; a call to this subroutine with a string argument of just one char, + ; by just one call to cx16.CHROUT of that single char. + %asm {{ + sta P8ZP_SCRATCH_B1 + sty P8ZP_SCRATCH_REG + ldy #0 +- lda (P8ZP_SCRATCH_B1),y + beq + + jsr cx16.CHROUT + iny + bne - ++ rts + }} +} + +asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) { + ; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total) + %asm {{ + stx P8ZP_SCRATCH_REG_X + jsr cx16utils.ubyte2decimal + pha + tya + jsr cx16.CHROUT + pla + jsr cx16.CHROUT + txa + jsr cx16.CHROUT + ldx P8ZP_SCRATCH_REG_X + rts + }} +} + +asmsub print_ub (ubyte value @ A) clobbers(A,Y) { + ; ---- print the ubyte in A in decimal form, without left padding 0s + %asm {{ + stx P8ZP_SCRATCH_REG_X + jsr cx16utils.ubyte2decimal +_print_byte_digits + pha + cpy #'0' + beq + + tya + jsr cx16.CHROUT + pla + jsr cx16.CHROUT + jmp _ones ++ pla + cmp #'0' + beq _ones + jsr cx16.CHROUT +_ones txa + jsr cx16.CHROUT + ldx P8ZP_SCRATCH_REG_X + rts + }} +} + +asmsub print_b (byte value @ A) clobbers(A,Y) { + ; ---- print the byte in A in decimal form, without left padding 0s + %asm {{ + stx P8ZP_SCRATCH_REG_X + pha + cmp #0 + bpl + + lda #'-' + jsr cx16.CHROUT ++ pla + jsr cx16utils.byte2decimal + jsr print_ub._print_byte_digits + ldx P8ZP_SCRATCH_REG_X + rts + }} +} + +asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) { + ; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well) + %asm {{ + stx P8ZP_SCRATCH_REG_X + bcc + + pha + lda #'$' + jsr cx16.CHROUT + pla ++ jsr cx16utils.ubyte2hex + jsr cx16.CHROUT + tya + jsr cx16.CHROUT + ldx P8ZP_SCRATCH_REG_X + rts + }} +} + +asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) { + ; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well) + %asm {{ + stx P8ZP_SCRATCH_REG_X + sta P8ZP_SCRATCH_B1 + bcc + + lda #'%' + jsr cx16.CHROUT ++ ldy #8 +- lda #'0' + asl P8ZP_SCRATCH_B1 + bcc + + lda #'1' ++ jsr cx16.CHROUT + dey + bne - + ldx P8ZP_SCRATCH_REG_X + rts + }} +} + +asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) { + ; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well) + %asm {{ + pha + tya + jsr print_ubbin + pla + clc + jmp print_ubbin + }} +} + +asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) { + ; ---- print the uword in A/Y in hexadecimal form (4 digits) + ; (if Carry is set, a radix prefix '$' is printed as well) + %asm {{ + pha + tya + jsr print_ubhex + pla + clc + jmp print_ubhex + }} +} + +asmsub print_uw0 (uword value @ AY) clobbers(A,Y) { + ; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total) + %asm {{ + stx P8ZP_SCRATCH_REG_X + jsr cx16utils.uword2decimal + ldy #0 +- lda cx16utils.uword2decimal.decTenThousands,y + beq + + jsr cx16.CHROUT + iny + bne - ++ ldx P8ZP_SCRATCH_REG_X + rts + }} +} + +asmsub print_uw (uword value @ AY) clobbers(A,Y) { + ; ---- print the uword in A/Y in decimal form, without left padding 0s + %asm {{ + stx P8ZP_SCRATCH_REG_X + jsr cx16utils.uword2decimal + ldx P8ZP_SCRATCH_REG_X + ldy #0 +- lda cx16utils.uword2decimal.decTenThousands,y + beq _allzero + cmp #'0' + bne _gotdigit + iny + bne - + +_gotdigit + jsr cx16.CHROUT + iny + lda cx16utils.uword2decimal.decTenThousands,y + bne _gotdigit + rts +_allzero + lda #'0' + jmp cx16.CHROUT + }} +} + +asmsub print_w (word value @ AY) clobbers(A,Y) { + ; ---- print the (signed) word in A/Y in decimal form, without left padding 0's + %asm {{ + cpy #0 + bpl + + pha + lda #'-' + jsr cx16.CHROUT + tya + eor #255 + tay + pla + eor #255 + clc + adc #1 + bcc + + iny ++ jmp print_uw + }} +} + +asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) { + ; ---- safe wrapper around PLOT kernel routine, to save the X register. + %asm {{ + stx P8ZP_SCRATCH_REG_X + tax + clc + jsr cx16.PLOT + ldx P8ZP_SCRATCH_REG_X + rts + }} +} + + + +} ; ---- end block screen diff --git a/compiler/res/prog8lib/math.p8 b/compiler/res/prog8lib/math.p8 index ed9692cf1..b13b000fc 100644 --- a/compiler/res/prog8lib/math.p8 +++ b/compiler/res/prog8lib/math.p8 @@ -4,8 +4,6 @@ ; ; indent format: TABS, size=8 -%import c64lib - math { %asminclude "library:math.asm", "" } diff --git a/compiler/res/prog8lib/prog8lib.p8 b/compiler/res/prog8lib/prog8lib.p8 index 88e279ca3..dff8d003a 100644 --- a/compiler/res/prog8lib/prog8lib.p8 +++ b/compiler/res/prog8lib/prog8lib.p8 @@ -4,8 +4,6 @@ ; ; indent format: TABS, size=8 -%import c64lib - prog8_lib { %asminclude "library:prog8lib.asm", "" } diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index 961fb28d5..d1e4f973d 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -8,6 +8,7 @@ import prog8.compiler.target.CompilationTarget import prog8.compiler.target.c64.C64MachineDefinition import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.codegen.AsmGen +import prog8.compiler.target.cx16.CX16MachineDefinition import prog8.parser.ParsingFailedError import java.io.IOException import java.nio.file.FileSystems @@ -52,7 +53,7 @@ private fun compileMain(args: Array) { when(compilationTarget) { "c64" -> { with(CompilationTarget) { - name = "c64" + name = "Commodore-64" machine = C64MachineDefinition encodeString = { str, altEncoding -> if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true) @@ -63,8 +64,21 @@ private fun compileMain(args: Array) { asmGenerator = ::AsmGen } } + "cx16" -> { + with(CompilationTarget) { + name = "Commander X16" + machine = CX16MachineDefinition + encodeString = { str, altEncoding -> + if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true) + } + decodeString = { bytes, altEncoding -> + if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true) + } + asmGenerator = ::AsmGen + } + } else -> { - System.err.println("invalid compilation target") + System.err.println("invalid compilation target. Available are: c64, cx16") exitProcess(1) } } @@ -121,20 +135,7 @@ private fun compileMain(args: Array) { if (compilationResult.programName.isEmpty()) println("\nCan't start emulator because no program was assembled.") else if(startEmulator) { - for(emulator in listOf("x64sc", "x64")) { - println("\nStarting C-64 emulator $emulator...") - val cmdline = listOf(emulator, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list", - "-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg") - val processb = ProcessBuilder(cmdline).inheritIO() - val process: Process - try { - process=processb.start() - } catch(x: IOException) { - continue // try the next emulator executable - } - process.waitFor() - break - } + CompilationTarget.machine.launchEmulator(compilationResult.programName) } } } diff --git a/compiler/src/prog8/compiler/Main.kt b/compiler/src/prog8/compiler/Main.kt index 4c94ca94f..98e3a0e03 100644 --- a/compiler/src/prog8/compiler/Main.kt +++ b/compiler/src/prog8/compiler/Main.kt @@ -90,11 +90,8 @@ private fun parseImports(filepath: Path, errors: ErrorReporter): Triple var zeropage: Zeropage val initSystemProcname: String + val cpu: String fun initializeZeropage(compilerOptions: CompilationOptions) fun getFloat(num: Number): IMachineFloat fun getFloatRomConst(number: Double): String? + fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) + fun launchEmulator(programName: String) } diff --git a/compiler/src/prog8/compiler/target/c64/AssemblyProgram.kt b/compiler/src/prog8/compiler/target/c64/AssemblyProgram.kt index 00be276ae..8d2611a11 100644 --- a/compiler/src/prog8/compiler/target/c64/AssemblyProgram.kt +++ b/compiler/src/prog8/compiler/target/c64/AssemblyProgram.kt @@ -22,7 +22,7 @@ class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyPro val outFile = when (options.output) { OutputType.PRG -> { command.add("--cbm-prg") - println("\nCreating C-64 prg.") + println("\nCreating prg.") prgFile } OutputType.RAW -> { diff --git a/compiler/src/prog8/compiler/target/c64/C64MachineDefinition.kt b/compiler/src/prog8/compiler/target/c64/C64MachineDefinition.kt index 790dbcdbb..f11856775 100644 --- a/compiler/src/prog8/compiler/target/c64/C64MachineDefinition.kt +++ b/compiler/src/prog8/compiler/target/c64/C64MachineDefinition.kt @@ -1,17 +1,19 @@ package prog8.compiler.target.c64 -import prog8.compiler.CompilationOptions -import prog8.compiler.CompilerException -import prog8.compiler.Zeropage -import prog8.compiler.ZeropageType +import prog8.ast.Program +import prog8.compiler.* import prog8.compiler.target.IMachineDefinition import prog8.compiler.target.IMachineFloat +import prog8.parser.ModuleImporter +import java.io.IOException import java.math.RoundingMode import kotlin.math.absoluteValue import kotlin.math.pow internal object C64MachineDefinition: IMachineDefinition { + override val cpu = "6502" + // 5-byte cbm MFLPT format limitations: override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255 override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255 @@ -64,6 +66,31 @@ internal object C64MachineDefinition: IMachineDefinition { return null } + override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) { + // if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries + if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) { + importer.importLibraryModule(program, "c64lib") + importer.importLibraryModule(program, "c64utils") + } + } + + override fun launchEmulator(programName: String) { + for(emulator in listOf("x64sc", "x64")) { + println("\nStarting C-64 emulator $emulator...") + val cmdline = listOf(emulator, "-silent", "-moncommands", "$programName.vice-mon-list", + "-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg") + val processb = ProcessBuilder(cmdline).inheritIO() + val process: Process + try { + process=processb.start() + } catch(x: IOException) { + continue // try the next emulator executable + } + process.waitFor() + break + } + } + override fun initializeZeropage(compilerOptions: CompilationOptions) { zeropage = C64Zeropage(compilerOptions) } @@ -143,11 +170,11 @@ internal object C64MachineDefinition: IMachineDefinition { free.clear() } } - assert(SCRATCH_B1 !in free) - assert(SCRATCH_REG !in free) - assert(SCRATCH_REG_X !in free) - assert(SCRATCH_W1 !in free) - assert(SCRATCH_W2 !in free) + require(SCRATCH_B1 !in free) + require(SCRATCH_REG !in free) + require(SCRATCH_REG_X !in free) + require(SCRATCH_W1 !in free) + require(SCRATCH_W2 !in free) for (reserved in options.zpReserved) reserve(reserved) diff --git a/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt index 65bde45f4..48f5a6cd9 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen/AsmGen.kt @@ -78,11 +78,13 @@ internal class AsmGen(private val program: Program, private fun header() { val ourName = this.javaClass.name - out("; 6502 assembly code for '${program.name}'") + val cpu = CompilationTarget.machine.cpu + + out("; $cpu assembly code for '${program.name}'") out("; generated by $ourName on ${LocalDateTime.now().withNano(0)}") out("; assembler syntax is for the 64tasm cross-assembler") out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}") - out("\n.cpu '6502'\n.enc 'none'\n") + out("\n.cpu '$cpu'\n.enc 'none'\n") program.actualLoadAddress = program.definedLoadAddress if (program.actualLoadAddress == 0) // fix load address diff --git a/compiler/src/prog8/compiler/target/cx16/CX16MachineDefinition.kt b/compiler/src/prog8/compiler/target/cx16/CX16MachineDefinition.kt new file mode 100644 index 000000000..9c7a62543 --- /dev/null +++ b/compiler/src/prog8/compiler/target/cx16/CX16MachineDefinition.kt @@ -0,0 +1,125 @@ +package prog8.compiler.target.cx16 + +import prog8.ast.Program +import prog8.compiler.* +import prog8.compiler.target.IMachineDefinition +import prog8.compiler.target.c64.C64MachineDefinition +import prog8.parser.ModuleImporter +import java.io.IOException + +internal object CX16MachineDefinition: IMachineDefinition { + + override val cpu = "65c02" + + // 5-byte cbm MFLPT format limitations: + override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255 + override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255 + override val FLOAT_MEM_SIZE = 5 + override val POINTER_MEM_SIZE = 2 + override val BASIC_LOAD_ADDRESS = 0x0801 + override val RAW_LOAD_ADDRESS = 0x8000 + + // the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations) + // and some heavily used string constants derived from the two values above + override val ESTACK_LO = 0x0400 // $0400-$04ff inclusive + override val ESTACK_HI = 0x0500 // $0500-$05ff inclusive + + override lateinit var zeropage: Zeropage + override val initSystemProcname = "cx16.init_system" + + override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num) + + override fun getFloatRomConst(number: Double): String? = null // TODO Does Cx16 have ROM float locations? + override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) { + // if we're producing a PRG or BASIC program, include the cx16utils and cx16lib libraries + if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) { + importer.importLibraryModule(program, "cx16lib") + importer.importLibraryModule(program, "cx16utils") + } + } + + override fun launchEmulator(programName: String) { + for(emulator in listOf("x16emu")) { + println("\nStarting Commander X16 emulator $emulator...") + val cmdline = listOf(emulator, "-rom", "/usr/share/x16-rom/rom.bin", "-scale", "2", + "-run", "-prg", programName + ".prg") + val processb = ProcessBuilder(cmdline).inheritIO() + val process: Process + try { + process=processb.start() + } catch(x: IOException) { + continue // try the next emulator executable + } + process.waitFor() + break + } + } + + override fun initializeZeropage(compilerOptions: CompilationOptions) { + zeropage = CX16Zeropage(compilerOptions) + } + + // 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names + // TODO add 65C02 opcodes + override val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs", + "beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc", + "cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey", + "eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs", + "inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las", + "lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php", + "pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx", + "sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre", + "sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa") + + + internal class CX16Zeropage(options: CompilationOptions) : Zeropage(options) { + + override val SCRATCH_B1 = 0x79 // temp storage for a single byte + override val SCRATCH_REG = 0x7a // temp storage for a register + override val SCRATCH_REG_X = 0x7b // temp storage for register X (the evaluation stack pointer) + override val SCRATCH_W1 = 0x7c // temp storage 1 for a word $7c+$7d + override val SCRATCH_W2 = 0x7e // temp storage 2 for a word $7e+$7f + + + override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) { + ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT + ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET + else -> ExitProgramStrategy.SYSTEM_RESET + } + + + init { + if (options.floats && options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE )) + throw CompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'") + + when (options.zeropage) { + ZeropageType.FULL -> { + free.addAll(0x02..0xff) + free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1)) + } + ZeropageType.KERNALSAFE -> { + free.addAll(0x02..0x7f) + free.addAll(0xa9..0xff) + free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1)) + } + ZeropageType.BASICSAFE -> { + free.addAll(0x02..0x7f) + free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1)) + } + ZeropageType.DONTUSE -> { + free.clear() // don't use zeropage at all + } + else -> throw CompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}") + } + + require(SCRATCH_B1 !in free) + require(SCRATCH_REG !in free) + require(SCRATCH_REG_X !in free) + require(SCRATCH_W1 !in free) + require(SCRATCH_W2 !in free) + + for (reserved in options.zpReserved) + reserve(reserved) + } + } +} diff --git a/compiler/src/prog8/parser/ModuleParsing.kt b/compiler/src/prog8/parser/ModuleParsing.kt index 8a21c85d2..809722550 100644 --- a/compiler/src/prog8/parser/ModuleParsing.kt +++ b/compiler/src/prog8/parser/ModuleParsing.kt @@ -94,7 +94,7 @@ internal class ModuleImporter { private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path { val fileName = "$name.p8" - val locations = mutableListOf(source.parent) + val locations = if(source.toString().isEmpty()) mutableListOf() else mutableListOf(source.parent) val propPath = System.getProperty("prog8.libdir") if(propPath!=null) @@ -109,7 +109,7 @@ internal class ModuleImporter { if (Files.isReadable(file)) return file } - throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)") + throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: embedded libs and $locations)") } private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? { @@ -128,10 +128,7 @@ internal class ModuleImporter { if(resource!=null) { // load the module from the embedded resource resource.use { - if(import.args[0].int==42) - println("importing '$moduleName' (library, auto)") - else - println("importing '$moduleName' (library)") + println("importing '$moduleName' (library)") importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true) } } else { diff --git a/examples/test.p8 b/examples/test.p8 index 4d8db20b8..7f02106d1 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,4 +1,3 @@ -%import c64utils %zeropage basicsafe main { @@ -8,25 +7,25 @@ main { uword xx = $ef34 xx &= $00f0 - c64scr.print_uwhex(xx, 1) - c64.CHROUT('\n') + screen.print_uwhex(xx, 1) + cx16.CHROUT('\n') xx |= $000f - c64scr.print_uwhex(xx, 1) - c64.CHROUT('\n') + screen.print_uwhex(xx, 1) + cx16.CHROUT('\n') xx ^= $0011 - c64scr.print_uwhex(xx, 1) - c64.CHROUT('\n') + screen.print_uwhex(xx, 1) + cx16.CHROUT('\n') xx = $ef34 xx &= $f000 - c64scr.print_uwhex(xx, 1) - c64.CHROUT('\n') + screen.print_uwhex(xx, 1) + cx16.CHROUT('\n') xx |= $0f00 - c64scr.print_uwhex(xx, 1) - c64.CHROUT('\n') + screen.print_uwhex(xx, 1) + cx16.CHROUT('\n') xx ^= $1100 - c64scr.print_uwhex(xx, 1) - c64.CHROUT('\n') + screen.print_uwhex(xx, 1) + cx16.CHROUT('\n') } }