============================================================================
Learning ML - Part 3
by Craig Taylor (duck@pembvax1.pembroke.edu)

  Last time we used a routine at $FFD2 which would print out the character code
contained within the accumalator.  That location will always print the character
out regardless of VIC-20, C=64, C=128 and even PET because Commodore decided 
to set up some locations in high memory that would perform routines that are
commonly needed.  

  Take a look now at the KERNAL 64/128 article and glance over some of the 
routines and their function / purpose. This article is meant to be a companion
to that article so you may want to flip back and forth as the discussion 
of the program listed below is discussed.

  Note that I've borrowed Craig Bruce's notation of having listings inside. To
extract the source that follows enter the following command on a Unix system:

grep '^\.@...\!' Hack3 | sed 's/^.@...\!.//' | sed 's/.@...\!//' >dir.asm

.@001! ;
.@002! ; Set up computer type for computer-dependant code / 
.@003! ;    Only used in displaying # routine / start of assembly setting.
.@004! ; BUDDY format.
.@005! ;
.@006! computer = 128             ; Define as either 64 or 128.

  For both c64 and c128 users the following code works.  Within the code is 
conditional assembly which means it will work on either computer assuming that
the computer is equal to either 128 or 64.

.@007! 
.@008! .if computer-64            ;** if computer not c64 then
.@009!       .org $1300          ;   and also make sure in BANK 15 when calling
.@010!                           ;   these routines.
.@011! .else                      ;** else if _is_ c64, then
.@012!       .org $c000
.@013! .ife                       ;** end of computer-dependant code.

  Because of this (the source is in BUDDY format) the C64 and C128 are set to 
assemble at different memory locations. On the C64, $c000 is 49152. On the C128 
it is at 4864. Note for the C128 it is necessary to do a BANK15 before executing
the code.
          
.@014!       .mem                ; - assemble to memory.

  This tells the assembler to actually put the code into memory.

.@015!
.@016! ;;-----------------------------------------------------------------------
.@017! ;; KERNAL EQUATES
.@018! ;;---------------------------------------------------------------------
.@019! 
.@020! setnam = $ffbd
.@021! setlfs = $ffba
.@022! open   = $ffc0
.@023! close  = $ffc3
.@024! chkin  = $ffc6
.@025! chrin  = $ffcf
.@026! bsout  = $ffd2
.@027! clrch  = $ffcc
.@028!

  These are the KERNAL routines we will actually be using. Their actual 
use will be documented when we come across them within the code.  

.@029! ;;-----------------------------------------------------------------------
.@030!
.@031! temp   = 253
.@032! charret = $0d
.@033! space = $20
.@034!

  Temp is set up to just be a temporary location in zero-page. Location 253 on
both the C64 and C128 is unused.  Charret stands for the carriage return
character and is the equivlent of a chr$(13). Space stands for the code for a 
space (a chr$(32))

.@035! ;;---------------------------------------------------------------------
.@036!
.@037! start = *
.@038!
.@039!    jsr read'dir       ; Initial jump table -- Note: Will read error after
.@040!    jmp read'err       ;     showing directory.
.@041!

  You'll see code like this a lot -- Basically we're building what is known as a
jump table. That way if we add more code to the directory or error routine we
don't have to worry about our SYS call's changing. To read the directory just
SYS base, to read the error channel just SYS base+3 (where BASE is 49152 on the
C64, 4864 on the 128)... 

  Also the JSR JMP combination may seem a little strange but what we are doing
is treating the directory routine as a subroutine and then JUMPING to the 
error routine. Once we do that the RTS in read'err will return us back to basic.

.@042! ;;----------------------------------------------------------------------
.@043!
.@044! read'dir = *
.@045! 
.@046! ; Opens and reads directory as a basic program.
.@047! ;==
.@048! ; Basic programs are read in as follows:
.@049! ;                  [Ptr to Next Line]:2 [Line #]:2 [Text]:.... [$00 byte]
.@050! ;                  ^^^^^^^^^^^REPEATS^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.@051! ; The end of a program is signifed by the $00 byte signifying end of text
.@052! ;    and the ptr's also being = $00.
.@053! ;==

  There are several ways to read the directory in machine language.  What we are
doing here is taking advantage of the drive's ability to allow us to load the
directory as a basic program -*except*- we aren't loading it per se. We're 
gonna grab each byte as it comes from the drive and interpret it ourselves
instead of putting it in memory as would normally be done.

  Basic programs are stored as the following: A 2 byte pointer, a 2 byte
line #, the basic text, and a null terminator and then starting over
from the 2 byte pointer.  The pointer we do not need, the line # is the number
of blocks the file takes up and the TEXT is the program name and file type. We
know when we're finished on the line by checking for a $00 byte.

.@054!                                ; Begin by opening up the 
.@055!                                ; directory file ("$").
.@056!        lda #$01                ;   length is 1        
.@057!        ldx #<dir               ;   lo byte pointer to file name.
.@058!        ldy #>dir               ;   hi byte pointer to file name.
.@059!        jsr setnam              ; - call setnam

  Okay, first we need to simulate opening the directory as a program file.
SETNAM sets up the filename for the open command.  In effect we are giving the 
basic syntax of open file#,device#,channel#,"filename" in reverse.

.@060!        lda #$01                ;   file # 1
.@061!        ldx #$08                ;   device # 8
.@062!        ldy #$00                ;   channel # 0
.@063!        jsr setlfs              ; - call setlfs

  Here we specify the device #, file #, channel # in preperation for the open.

.@064!        jsr open                ; - call open

  Open up the file. This is the routine that does the real work. SETNAM and
SETLFS were preparatory routines for this.

.@065!  ;
.@066!  ; read in the bytes and display (skipping line links etc)
.@067!  ;
.@068!        ldx #$01               ;   file #1
.@069!        jsr chkin              ; - call chkin to set input file #.

  Now we need to specify the input file # and tell the computer that all further
chrin's are to be from file #1. (By default, it would have read from the 
keyboard unless we had this here).

.@070!        jsr chrin              ; - ignore starting address (2 bytes)
.@071!        jsr chrin 

  Skip the starting address -- When reading the directory it is not relevant
so read the bytes and discard them.

.@072!  skip  jsr chrin              ; - ignore pointer to next line (2 bytes)

  Now we skip the pointer for the next line. This is only used when loading
basic programs to re-link the lines. When listing the directory they are not
needed.

.@073!  bck1  jsr chrin

  This is still part of the routine that skips the pointer to the next line, yet
it has a label used below that allows us to check for end of file more easily.

.@074!  line  jsr chrin              ; - get line # lo.
.@075!        sta temp               ; - store lo of line # @ temp
.@076!        jsr chrin              ; - get hi of line #

  Here we get the line # as the next 2 bytes in the file.

.@077! 
.@078! .if computer-64               ; * if C128 then

  Unfortunately C= did not provide a nice routine in the KERNAL to display 
numeric values - however - by exploring inside the operating system a way to 
display numbers is there.  Note that the following may look confusing -- if it
does just rest assured it will print out the line # correctly.
  
.@079!        sta $61
.@080!        ldy temp
.@081!        sty $60
.@082!        lda #$00
.@083!        sta $63
.@084!        ldy temp                ;   store values for conversion.
.@085!        jsr $ba07               ; - MONITOR routine: convert to BCD values
.@086!        lda #$00
.@087!        ldx #$08
.@088!        ldy #$03
.@089!        jsr $ba5d               ; - MONITOR routine: print BCD 
.@090!                                ;values in decimal

  This is the C128 version which uses some of the MONITOR routines to display
the numeric block size.

.@091! .else                          ; * else if c64
.@092!        ldx temp 
.@093!        jsr $bdcd               ; - print line # (w/in ROM routine).
.@094! .ife                           ; * end of computer dependant code.

  This is the C64 code to display a numeric value (notice how much simplified it
is over the C128)...

.@095!
.@096!        lda #space
.@097!        jsr bsout               ; - print space

  Let's print a space between the filename and the block size.

.@098!  gtasc jsr chrin               ; - start printing filename until 
.@099!                                ;end of line.
.@100!        beq chck                ;   (Zero signifies eol).
.@101!        jsr bsout               ; - Print character
.@102!        sec
.@103!        bcs gtasc               ;   and jump back.

  Now we start getting a character (line #98), if zero we branch out of the loop
(line #100), else we display the character (#101), and jump back (#102-03).

.@104!  chck  lda #charret            ; - Else we need to start the next line
.@105!        jsr bsout               ;   Print a carriage return.

  Ah, we got to a null byte so that's the end of this line - display a car/ret.

.@106!        jsr chrin               ; - And get the next pointer
.@107!        bne bck1                ;   If non-zero go, strip other ptr,
.@108!                                ; and continue.

  This is where we branch back -- we are checking here for 2 null bytes on 
input.  We get the first byte of the pointer and if it's non-zero then we know
it's not the end of the directory so we jump back to discard the second byte at
line #73.

.@109!        jsr chrin               ; - Else check 2nd byte of pointer
.@110!        bne line                ;   as if both 0 then = end of directory.

  This is a continuation of the checking above. This time we're getting the 
2nd byte and checking for 0.  If it's not we jump back to get and display the
line # etc. If it is 0 then that means we had $0000 for the next pointer which
means that it's the end of the directory.

.@111!  ;
.@112!  ;had 3 0's in a row so end of prog
.@113!  ;now close the file.
.@114!  ;
.@115!        lda #$01                ;   file # to close
.@116!        jsr close               ; - so close it
.@117!        jsr clrch               ; - clear all channels
.@118!        rts                     ; - and return to basic
.@119!

  We then close the file by specifying the file # and calling close. We then 
tell the computer to reset all the default input / output devices by calling
clrch (remember we changed the default input channel??). And then we can return
to where this routine was called from.

.@120! ; FILENAME string
.@121! dir    .asc "$"

  This is the string that is pointed to by the SETNAM call. Note that a search
pattern could be set by
      line#121:       .asc "$hack*" 
and by changing the length set in .A in the call in line #56.

.@122!
.@123! ;;-----------------------------------------------------------------------
.@124!
.@125! read'err = *
.@126! 
.@127! ; This routine simply grabs bytes from a channel 15 it opens up until
.@128! ;   a car/ret byte is found. Then it closes and returns.
.@129! 

  Reading the error channel is much much more simpler than reading the 
directory.  Basically we just open up the channel (specifying a null name) and
repeatadly get bytes until a car/ret is found.

.@130! rderr  lda #$00                ;   length is 0
.@131!        jsr setnam              ; - call setname

  Setup so we don't specify a name (length = 0).

.@132!        lda #$0f                ;   file # (15)
.@133!        ldx #$08                ;   device # (08)
.@134!        ldy #$0f                ;   channel # (15)
.@135!        jsr setlfs              ; - set logical file #

  Do the equivlent of open 15,8,15.

.@136!        jsr open                ; - and open it.

  Open it.

.@137!  ;specify file as input
.@138!        ldx #$0f                ;   file 15 is input
.@139!        jsr chkin               ; - so specify it.

  Now set up file # 15 as input so we can start getting, displaying etc until
a car/ret is found.

.@140!  ;now read in file
.@141!  loop  jsr chrin               ; - read char
.@142!        jsr bsout               ; - print char
.@143!        cmp #charret            ;   is it return?
.@144!        bne loop                ; - if not jmp back

  Read in and display the characters from the error channel until a char/ret is
found.

.@145!  ;now close the file
.@146!        lda #$0f                ;   file #
.@147!        jsr close               ; - close the file
.@148!        jsr clrch               ;   restore i/o

  And once it is, we close the file and restore the default i/o settings.

.@149!  ;now return to basic
.@150!        rts

  And return to our caller, in this case - basic.

============================================================================
 
