- Converted WGSetCursor to use zero-page parameter passing

- Eliminated WGInverse and WGNormal API calls
- Added the basic dispatcher for ML API
- Trimmed some unneeded code
- Prototyped assembly language API file
- Another pass on reducing code size
- X register now reserved for dispatcher
- API calls using X register as a parameter converted to Y register
- Added WGSetGlobalCursor
- Some code cleanup
This commit is contained in:
Quinn Dunki 2014-10-03 12:22:19 -07:00
parent 7249f48d4a
commit 38da3a2c8e
12 changed files with 143 additions and 156 deletions

View File

@ -17,8 +17,8 @@ ADDRDEMO=6000
PGM=gui PGM=gui
DEMO=guidemo DEMO=guidemo
#all: $(DEMO) $(PGM) all: $(DEMO) $(PGM)
all: $(PGM) #all: $(PGM)
$(DEMO): $(DEMO):
@PATH=$(PATH):/usr/local/bin; $(CL65) -t apple2enh --start-addr $(ADDRDEMO) -l$(DEMO).lst $(DEMO).s @PATH=$(PATH):/usr/local/bin; $(CL65) -t apple2enh --start-addr $(ADDRDEMO) -l$(DEMO).lst $(DEMO).s
@ -31,7 +31,7 @@ $(PGM):
@PATH=$(PATH):/usr/local/bin; $(CL65) -t apple2enh --start-addr $(ADDR) -l$(PGM).lst $(PGM).s @PATH=$(PATH):/usr/local/bin; $(CL65) -t apple2enh --start-addr $(ADDR) -l$(PGM).lst $(PGM).s
java -jar $(AC) -d $(DEMO).dsk $(PGM) java -jar $(AC) -d $(DEMO).dsk $(PGM)
java -jar $(AC) -p $(DEMO).dsk $(PGM) BIN 0x$(ADDR) < $(PGM) java -jar $(AC) -p $(DEMO).dsk $(PGM) BIN 0x$(ADDR) < $(PGM)
# rm -f $(PGM) rm -f $(PGM)
rm -f $(PGM).o rm -f $(PGM).o
osascript V2Make.scpt $(PROJECT_DIR) $(DEMO) $(PGM) osascript V2Make.scpt $(PROJECT_DIR) $(DEMO) $(PGM)

View File

@ -5,6 +5,7 @@ Known issues
- Negative cursor positions unsupported - Negative cursor positions unsupported
- Positive scroll values unsupported - Positive scroll values unsupported
- Hitting Reset in app that uses windows and desktop (no mouse needed) seems to mess up screen holes for Disk II - Hitting Reset in app that uses windows and desktop (no mouse needed) seems to mess up screen holes for Disk II
- Mashing a button with mouse in Applesoft will cause Undefined Statement error
To Do: To Do:

View File

@ -595,17 +595,14 @@ WGAmpersand_CURSR:
jsr WGAmpersandBeginArguments jsr WGAmpersandBeginArguments
jsr WGAmpersandIntArgument jsr WGAmpersandIntArgument
pha sta PARAM0
jsr WGAmpersandNextArgument jsr WGAmpersandNextArgument
jsr WGAmpersandIntArgument jsr WGAmpersandIntArgument
pha sta PARAM1
jsr WGAmpersandEndArguments jsr WGAmpersandEndArguments
ply
plx
jsr WGSetCursor jsr WGSetCursor
rts rts
@ -721,7 +718,8 @@ WGAmpersand_ERASE:
; Sets text mode to normal ; Sets text mode to normal
; &NRML ; &NRML
WGAmpersand_NRML: WGAmpersand_NRML:
jsr WGNormal lda #CHAR_NORMAL
sta INVERSE
rts rts
@ -730,7 +728,8 @@ WGAmpersand_NRML:
; Sets text mode to inverse ; Sets text mode to inverse
; &INVR ; &INVR
WGAmpersand_INVR: WGAmpersand_INVR:
jsr WGInverse lda #CHAR_INVERSE
sta INVERSE
rts rts
@ -762,7 +761,7 @@ WGAmpersand_FILL:
pha pha
jsr WGAmpersandEndArguments jsr WGAmpersandEndArguments
plx ply
jsr WGFillRect jsr WGFillRect
jsr WGBottomCursor jsr WGBottomCursor

59
gui.s
View File

@ -9,19 +9,40 @@
.org $4000 .org $4000
; Common definitions ; Common definitions
.include "zeropage.s" .include "zeropage.s"
.include "switches.s" .include "switches.s"
.include "macros.s" .include "macros.s"
; Main
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Main entry point. BRUN will land here.
main: main:
jsr WGInit jsr WGInit
jsr WG80 rts ; Don't add any bytes here!
rts
; This is the non-negotiable entry point used by applications Don't move it!
; $4004
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WGDispatch
; The dispatcher for calling the assembly-language API from assembly programs
; X: API call number
; P0-3,Y: Parameters to call, as needed
WGDispatch:
jmp (WGEntryPointTable,x)
; Entry point jump table
WGEntryPointTable:
.addr WGClearScreen
.addr WGDesktop
.addr WGPlot
.addr WGSetCursor
.addr WGSetGlobalCursor
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -30,6 +51,7 @@ main:
WGInit: WGInit:
SAVE_AXY SAVE_AXY
jsr WG80
jsr WGInitApplesoft jsr WGInitApplesoft
ldy #15 ; Clear our block allocators ldy #15 ; Clear our block allocators
@ -59,9 +81,6 @@ WGInit_clearMemLoop:
WG80: WG80:
pha pha
; lda #3
; jsr $fe95
lda #$a0 lda #$a0
jsr $c300 jsr $c300
@ -74,31 +93,6 @@ WG80:
rts rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; waitForKey
; Spinlocks until a key is pressed
waitForKey:
lda KBDSTRB
bpl waitForKey
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; read80ColSwitch
; Returns value of the 80 col switch on //c and //c+ machines
; OUT A: Switch state (non-zero=80 cols)
; NOTE: Untested
read80ColSwitch:
lda $c060
bpl read80ColSwitch_40
lda #$1
rts
read80ColSwitch_40:
lda #$0
rts
; Code modules ; Code modules
.include "utility.s" .include "utility.s"
.include "painting.s" .include "painting.s"
@ -111,7 +105,6 @@ read80ColSwitch_40:
; Suppress some linker warnings - Must be the last thing in the file ; Suppress some linker warnings - Must be the last thing in the file
.SEGMENT "ZPSAVE" .SEGMENT "ZPSAVE"
.SEGMENT "EXEHDR" .SEGMENT "EXEHDR"

Binary file not shown.

View File

@ -10,23 +10,44 @@
.org $6000 .org $6000
; Reserved locations ; Reserved locations
PARAM0 = $06
PARAM1 = $07
; Constants PARAM2 = $08
PARAM3 = $09
; ROM entry points
; WeeGUI entry points ; WeeGUI entry points
WeeGUI = $4004
GUI_MAIN = $4000 WGClearScreen = 0
WGDesktop = 2
WGPlot = 4
WGSetCursor = 6
WGSetGlobalCursor = 8
; Main ; Sample code
main: main:
ldx #WGClearScreen
jsr WeeGUI
ldx #WGDesktop
jsr WeeGUI
lda #40
sta PARAM0
lda #12
sta PARAM1
ldx #WGSetGlobalCursor
jsr WeeGUI
lda #'Q'+$80
ldx #WGPlot
jsr WeeGUI
rts
.if 0
;jmp tortureTestPrint ;jmp tortureTestPrint
;jmp tortureTestRects ;jmp tortureTestRects
@ -144,8 +165,10 @@ testPaintContents:
;; ;;
jsr WGNormal jsr WGNormal
ldx #10 lda #10
ldy #15 sta PARAM0
lda #15
sta PARAM1
jsr WGSetCursor jsr WGSetCursor
CALL16 WGPrint,testStr CALL16 WGPrint,testStr
@ -156,6 +179,8 @@ testPaintContents:
ldy #0 ldy #0
testPaintContents_loop: testPaintContents_loop:
ldx #0 ldx #0
stx PARAM0
sty PARAM1
jsr WGSetCursor jsr WGSetCursor
tya tya
@ -217,7 +242,7 @@ testTitle2:
testTitle3: testTitle3:
.byte "More Magic",0 .byte "More Magic",0
.endif
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -118,22 +118,6 @@
.endmacro .endmacro
.macro PARAMQUAD p0,p1,p2,p3
lda #p0
sta PARAM0
lda #p1
sta PARAM1
lda #p2
sta PARAM2
lda #p3
sta PARAM3
.endmacro
.macro CALLQUAD func,p0,p1,p2,p3
PARAMQUAD p0,p1,p2,p3
jsr func
.endmacro
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Rendering macros ; Rendering macros
; ;

View File

@ -82,13 +82,13 @@ WGDesktop_charLoop:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WGPlot ; WGPlot
; Plots a character at current cursor position (assumes 80 cols) ; Plots a character at global cursor position (assumes 80 cols)
; A: Character to plot (Apple format) ; A: Character to plot (Apple format)
; Side effects: Clobbers S0, BASL,BASH ; Side effects: Clobbers S0, BASL,BASH
; ;
WGPlot: WGPlot:
sta SCRATCH0 sta SCRATCH0
SAVE_AXY SAVE_XY
ldx WG_CURSORY ldx WG_CURSORY
lda TEXTLINES_L,x ; Compute video memory address of point lda TEXTLINES_L,x ; Compute video memory address of point
@ -110,19 +110,17 @@ WGPlot:
bne WGPlot_xOdd bne WGPlot_xOdd
SETSWITCH PAGE2ON ; Plot the character SETSWITCH PAGE2ON ; Plot the character
ldy #$0
lda SCRATCH0 lda SCRATCH0
sta (BASL),y sta (BASL)
jmp WGPlot_done jmp WGPlot_done
WGPlot_xOdd: WGPlot_xOdd:
SETSWITCH PAGE2OFF ; Plot the character SETSWITCH PAGE2OFF ; Plot the character
ldy #$0
lda SCRATCH0 lda SCRATCH0
sta (BASL),y sta (BASL)
WGPlot_done: WGPlot_done:
RESTORE_AXY RESTORE_XY
rts rts
@ -146,16 +144,8 @@ WGPrint:
sta SCRATCH1 sta SCRATCH1
LDX_ACTIVEVIEW ; Cache view width for later LDX_ACTIVEVIEW ; Cache view width for later
inx lda WG_VIEWRECORDS+7,x
inx
inx
inx
inx
inx
inx
lda WG_VIEWRECORDS,x
sta WG_SCRATCHA sta WG_SCRATCHA
inx ; Leave X pointing at view height, for later quick access
ldy #0 ldy #0
@ -168,10 +158,10 @@ WGPrint_lineLoopFirst: ; Calculating start of first line is slightly different
cmp WG_VIEWCLIP+0 cmp WG_VIEWCLIP+0
bcs WGPrint_visibleChars bcs WGPrint_visibleChars
lda WG_VIEWCLIP+0 lda WG_VIEWCLIP+0 ; Line begins before left clip plane
sec ; Line begins before left clip plane sec ; Advance string index and advance cursor into clip box
sbc WG_LOCALCURSORX sbc WG_LOCALCURSORX
tay ; Advance string index and advance cursor into clip box tay
lda WG_VIEWCLIP+0 lda WG_VIEWCLIP+0
sta WG_LOCALCURSORX sta WG_LOCALCURSORX
bra WGPrint_visibleChars bra WGPrint_visibleChars
@ -244,7 +234,7 @@ WGPrint_charLoopNormal:
beq WGPrint_endVisible beq WGPrint_endVisible
bra WGPrint_charLoopNormal bra WGPrint_charLoopNormal
WGPrint_done: ; This is up here to keep local branches in range WGPrint_done: ; This is in the middle here to keep local branches in range
RESTORE_ZPS RESTORE_ZPS
RESTORE_AXY RESTORE_AXY
rts rts
@ -262,14 +252,14 @@ WGPrint_nextLine:
lda WG_LOCALCURSORY lda WG_LOCALCURSORY
cmp WG_VIEWCLIP+3 ; Check for bottom clip plane cmp WG_VIEWCLIP+3 ; Check for bottom clip plane
beq WGPrint_done beq WGPrint_done
cmp WG_VIEWRECORDS,x ; Check for bottom of view cmp WG_VIEWRECORDS+8,x ; Check for bottom of view
beq WGPrint_done beq WGPrint_done
lda (PARAM0),y ; Check for end string landing exactly at line end lda (PARAM0),y ; Check for end string landing exactly at line end
beq WGPrint_done beq WGPrint_done
lda #0 ; Wrap to next line lda #0 ; Wrap to next line
sta WG_LOCALCURSORX sta WG_LOCALCURSORX
jmp WGPrint_lineLoop bra WGPrint_lineLoop
WGPrint_charLoopInverse: WGPrint_charLoopInverse:
lda (PARAM0),y ; Draw current character lda (PARAM0),y ; Draw current character
@ -282,7 +272,7 @@ WGPrint_charLoopInverse:
WGPrint_charLoopInverseLow: WGPrint_charLoopInverseLow:
and #%00111111 ; Normal inverse and #%00111111 ; Normal inverse
WGPrint_charLoopInversePlot: ; This is down here to keep local branches in range WGPrint_charLoopInversePlot:
jsr WGPlot jsr WGPlot
iny iny
@ -296,28 +286,3 @@ WGPrint_charLoopInversePlot: ; This is down here to keep local branches in range
beq WGPrint_endVisible beq WGPrint_endVisible
bra WGPrint_charLoopInverse bra WGPrint_charLoopInverse
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WGNormal
; Sets normal text rendering mode
;
WGNormal:
pha
lda #CHAR_NORMAL
sta INVERSE
pla
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WGInverse
; Sets inverse text rendering mode
;
WGInverse:
pha
lda #CHAR_INVERSE
sta INVERSE
pla
rts

35
rects.s
View File

@ -14,14 +14,12 @@
; PARAM1: Top edge ; PARAM1: Top edge
; PARAM2: Width ; PARAM2: Width
; PARAM3: Height ; PARAM3: Height
; X: Character to fill (Apple format) ; Y: Character to fill (Apple format)
; Side effects: Clobbers BASL,BASH ; Side effects: Clobbers Y,S0,BASL,BASH
; ;
WGFillRect: WGFillRect:
SAVE_AX
SAVE_AXY sty SCRATCH0
SAVE_ZPS
stx SCRATCH0
clc ; Compute bottom edge clc ; Compute bottom edge
lda PARAM1 lda PARAM1
@ -58,9 +56,9 @@ WGFillRect_vertLoop:
dey dey
phy ; We'll reuse this calculation for the odd columns phy ; We'll reuse this calculation for the odd columns
lda SCRATCH0 ; Prepare to plot
WGFillRect_horzLoopEvenAligned0: ; Draw even columns WGFillRect_horzLoopEvenAligned0: ; Draw even columns
lda SCRATCH0 ; Plot the character sta (BASL),y ; Plot the character
sta (BASL),y
dey dey
bpl WGFillRect_horzLoopEvenAligned0 ; Loop for w/2 bpl WGFillRect_horzLoopEvenAligned0 ; Loop for w/2
@ -68,8 +66,7 @@ WGFillRect_horzLoopEvenAligned0: ; Draw even columns
ply ; Iterate w/2 again ply ; Iterate w/2 again
WGFillRect_horzLoopEvenAligned1: ; Draw odd columns WGFillRect_horzLoopEvenAligned1: ; Draw odd columns
lda SCRATCH0 ; Plot the character sta (BASL),y ; Plot the character
sta (BASL),y
dey dey
bpl WGFillRect_horzLoopEvenAligned1 ; Loop for w/2 bpl WGFillRect_horzLoopEvenAligned1 ; Loop for w/2
@ -89,7 +86,7 @@ WGFillRect_horzLoopEvenAlignedEvenWidth:
dex dex
cpx PARAM1 cpx PARAM1
bcs WGFillRect_vertLoop bcs WGFillRect_vertLoop
jmp WGFillRect_done bra WGFillRect_done
WGFillRect_horzLoopOdd: WGFillRect_horzLoopOdd:
; CASE 2: Left edge odd-aligned, even width ; CASE 2: Left edge odd-aligned, even width
@ -99,9 +96,9 @@ WGFillRect_horzLoopOdd:
tay ; Iterate w/2 tay ; Iterate w/2
phy ; We'll reuse this calculation for the even columns phy ; We'll reuse this calculation for the even columns
lda SCRATCH0 ; Prepare to plot
WGFillRect_horzLoopOddAligned0: ; Draw even columns WGFillRect_horzLoopOddAligned0: ; Draw even columns
lda SCRATCH0 ; Plot the character sta (BASL),y ; Plot the character
sta (BASL),y
dey dey
bne WGFillRect_horzLoopOddAligned0 ; Loop for w/2 bne WGFillRect_horzLoopOddAligned0 ; Loop for w/2
@ -110,8 +107,7 @@ WGFillRect_horzLoopOddAligned0: ; Draw even columns
dey dey
WGFillRect_horzLoopOddAligned1: ; Draw even columns WGFillRect_horzLoopOddAligned1: ; Draw even columns
lda SCRATCH0 ; Plot the character sta (BASL),y ; Plot the character
sta (BASL),y
dey dey
bpl WGFillRect_horzLoopOddAligned1 ; Loop for w/2 bpl WGFillRect_horzLoopOddAligned1 ; Loop for w/2
@ -130,14 +126,10 @@ WGFillRect_horzLoopOddAlignedEvenWidth:
plx ; Prepare for next row plx ; Prepare for next row
dex dex
cpx PARAM1 cpx PARAM1
bcs WGFillRect_vertLoopJmp bcs WGFillRect_vertLoop
jmp WGFillRect_done
WGFillRect_vertLoopJmp:
jmp WGFillRect_vertLoop
WGFillRect_done: WGFillRect_done:
RESTORE_ZPS RESTORE_AX
RESTORE_AXY
rts rts
@ -178,7 +170,6 @@ CH_DOUBLE = '\'
WGStrokeRect: WGStrokeRect:
SAVE_AXY SAVE_AXY
SAVE_ZPS SAVE_ZPS

View File

@ -44,8 +44,9 @@ tortureTestPrint_init:
tortureTestPrint_loop: tortureTestPrint_loop:
phx phx
ldx #0 ; Initialize lda #0 ; Initialize
ldy #0 sta PARAM0
sta PARAM1
jsr WGSetCursor jsr WGSetCursor
plx plx
@ -187,7 +188,7 @@ tortureTestRectsEvenLoop:
dec dec
sta $0100,x sta $0100,x
ldx #'Q'+$80 ldy #'Q'+$80
jsr WGFillRect jsr WGFillRect
jsr WGStrokeRect jsr WGStrokeRect
@ -252,7 +253,7 @@ tortureTestRectsOddLoop:
dec dec
sta $0100,x sta $0100,x
ldx #'Q'+$80 ldy #'Q'+$80
jsr WGFillRect jsr WGFillRect
jsr WGStrokeRect jsr WGStrokeRect

46
views.s
View File

@ -383,7 +383,8 @@ paintCheck_plot: ; Paint our state
inc WG_CURSORX ; Prepare for title inc WG_CURSORX ; Prepare for title
inc WG_CURSORX inc WG_CURSORX
jsr WGNormal lda #CHAR_NORMAL
sta INVERSE
lda WG_VIEWRECORDS+12,y lda WG_VIEWRECORDS+12,y
sta PARAM0 sta PARAM0
@ -436,12 +437,14 @@ paintButton:
lda WG_VIEWRECORDS+9,y ; Is button highlighted? lda WG_VIEWRECORDS+9,y ; Is button highlighted?
and #$80 and #$80
bne paintButton_titleSelected bne paintButton_titleSelected
jsr WGNormal lda #CHAR_NORMAL
sta INVERSE
lda #' '+$80 lda #' '+$80
bra paintButton_titleMarginLeft bra paintButton_titleMarginLeft
paintButton_titleSelected: paintButton_titleSelected:
jsr WGInverse lda #CHAR_INVERSE
sta INVERSE
lda #' ' lda #' '
paintButton_titleMarginLeft: paintButton_titleMarginLeft:
@ -553,7 +556,7 @@ WGEraseView:
inc inc
sta PARAM3 sta PARAM3
ldx #' '+$80 ldy #' '+$80
jsr WGFillRect jsr WGFillRect
WGEraseView_done: WGEraseView_done:
@ -585,7 +588,7 @@ WGEraseViewContents:
lda WG_VIEWRECORDS,y lda WG_VIEWRECORDS,y
sta PARAM3 sta PARAM3
ldx #' '+$80 ldy #' '+$80
jsr WGFillRect jsr WGFillRect
WGEraseViewContents_done: WGEraseViewContents_done:
@ -950,15 +953,40 @@ WGViewSetAction_done:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WGSetCursor ; WGSetCursor
; Sets the current local view cursor ; Sets the current local view cursor
; X: X ; PARAM0: X
; Y: Y ; PARAM1: Y
; ;
WGSetCursor: WGSetCursor:
stx WG_LOCALCURSORX pha
sty WG_LOCALCURSORY
lda PARAM0
sta WG_LOCALCURSORX
lda PARAM1
sta WG_LOCALCURSORY
pla
rts rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WGSetGlobalCursor
; Sets the current global cursor
; PARAM0: X
; PARAM1: Y
;
WGSetGlobalCursor:
pha
lda PARAM0
sta WG_CURSORX
lda PARAM1
sta WG_CURSORY
pla
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WGSyncGlobalCursor ; WGSyncGlobalCursor
; Synchronizes the global cursor with the current local view's ; Synchronizes the global cursor with the current local view's

View File

@ -21,4 +21,4 @@ PARAM1 = $07
PARAM2 = $08 PARAM2 = $08
PARAM3 = $09 PARAM3 = $09
SCRATCH0 = $19 SCRATCH0 = $19
SCRATCH1 = $1A SCRATCH1 = $1a