OSBYTE 127, 139; OSRDCH entry point; OSCLI Handler; Table of star commands; *BASIC; Reading hex values from strings; Copyright string (backwards) - 479 bytes (2.9%)


§1. Introduction.

 The Command Line Interpreter is responsible for executing star commands. There are a number
 built in to the system (see .starCommandTable), and more can be added via paged ROMs.

 The commands can have parameters as integer decimal, hexadecimal values, and strings.
 These commands have characters in the range 32-126, but can encode control codes and other
 non-printable characters. It uses GSINIT and GSREAD to convert the set of ASCII printable
 string characters into a full 8-bit ASCII converted values. See .gsinitEntryPoint .

 This chapter contains integer (in string form) to byte conversion,
 see .parseDecimalNumberFromString. A conversion for a hex digit in string form to a byte,
 see .readHexDigit, which is used to read a wole 32 bit address, see .readOSFILEAddress .

 Historical note: Every Acorn computer from the System 2 onwards has a CLI, and the
 asterisk probably derives from the System 2's use of it as a prompt character.

§2. OSRDCH - Read a character.

       This reads from an EXEC file if open, or the current input buffer otherwise

 On Entry:
       A = 0 for reading a key instantly
       A = 255 for reading a key with a time limit
.osrdchEntryPoint = $dec5
    LDA #0                                              
.readCharacterTimed = $dec7
    STA .readCharacterTimedFlag                         store A (0 or 255) to indicate if
                                                        it's a timed read
    TXA                                                 }
    PHA                                                 }
    TYA                                                 } store X and Y
    PHA                                                 }
    LDY .execFileHandle                                 get *EXEC file handle
    BEQ .readCharacterFromCurrentInputBufferLoop        if (zero, i.e. no EXEC file open)
                                                        then branch

    read from an EXEC file
    SEC                                                 set carry
    ROR .tapeCritical                                   set bit 7 of tape filing system
                                                        active flag to prevent clashes
    JSR .OSBGET                                         get a byte from the file
    PHP                                                 push flags to preserve carry
    LSR .tapeCritical                                   clear .tapeCritical flag
    PLP                                                 get back flags
    BCC .pullAndReturn                                  if (character found) then branch
                                                        (recall values and exit)
    LDA #0                                              error reading from file, so close
                                                        the file
    STA .execFileHandle                                 store it in exec file handle
    JSR .OSFIND                                         and close file via OSFIND

.readCharacterFromCurrentInputBufferLoop = $dee6
    BIT .escapeFlag                                     check ESCAPE flag
    BMI .setEscapeConditionAndExit                      if (ESCAPE flag set) then branch
                                                        (set escape condition and exit)
    LDX .currentInputBuffer                             get current input buffer number
    JSR .readFromEconetOrSoftKeyOrInputBufferA          get a byte from current input buffer
    BCC .pullAndReturn                                  if (valid character found) then
                                                        branch (exit)

.checkForTiming = $def2
    BIT .readCharacterTimedFlag                         check for timed read
    BVC .readCharacterFromCurrentInputBufferLoop        if (this is not timed) then branch
                                                        (go back and read byte from input
                                                        buffer again)
    LDA .inkeyTimeoutCounterLow                         check timers
    ORA .inkeyTimeoutCounterHigh                        
    BNE .readCharacterFromCurrentInputBufferLoop        if (timer is not zero) then branch
                                                        (loop back and keep trying to read)
    BCS +                                               if (timed out) then branch (exit
                                                        without storing character read)

.setEscapeConditionAndExit = $df00
    SEC                                                 
    LDA #.charESCAPE                                    ESCAPE character read

.pullAndReturn = $df03
    STA .readCharacterTimedFlag                         remember character read
+
    PLA                                                 }
    TAY                                                 } restore X,Y
    PLA                                                 }
    TAX                                                 }
    LDA .readCharacterTimedFlag                         recall character read
    RTS                                                 

§3. Copyright string.

 Only used as part of the check for valid Paged ROM, never printed
.copyrightString = $df0c
    !text ")C(",0                                       Copyright string (backwards)

§4. Star command table.

 *. <directory>                Synonym of *CAT
 *FX <a>,<x>,<y>               Calls OSBYTE
 *BASIC                        Start BASIC
 *CAT <directory>              Show catalogue from filing system
 *CODE <x>,<y>                 Execute code via USERV with A=0
 *EXEC <filename>              Execute a script file
 *HELP <string>                Get help
 *KEY <n> <string>             Define a soft key definition
 *LINE <string>                Execute code via USERV with A=1
 *LOAD <filename> <addr>       Load file
 *MOTOR <n>                    Turn on/off cassette motor
 *OPT <x>,<y>                  Set filing system options (Equivalent to OSBYTE 139)
 *RUN <filename>               Load and execute a file from the current filing system
 *ROM                          Set ROM filing system
 *SAVE <filename> <start> <end> <exec> <load> Save memory to the current filing system
 *SPOOL <filename>             Save keystrokes to the current filing system
 *TAPE <n>                     Set the TAPE filing system
 *TV <x>,<y>                   Adjust the TV display

 Commands can be abbreviated, e.g. *M. for *MOTOR.

 This list is not quite alphabetical, so that short abbreviations yield the more common
 commands, so *LOAD comes before *LINE so that *L. becomes *LOAD; and *R. becomes *RUN,
 not *ROM.

 The last entry is empty to catch anything else.

 After the name and address of each command is a single byte parameter that is passed in
 the accumulator before calling the routine's address. If the parameter has the top bit
 clear (*.; *BASIC; *CAT; *EXEC; *LOAD; *LINE; *RUN; *SAVE; *SPOOL), then the next part of
 the command line is decoded into a string (usually a pathname).

 A group of commands (*CODE; *MOTOR; *OPT; *TAPE; *ROM; *TV) are synonyms for *FX calls
 with the parameter being the OSBYTE number.

 Historical note: The Acorn System 2 star commands were *BASIC, *CAT, *LOAD, *SAVE, *GO,
 *RUN, *MEM, *MON, *NOMON, *DRIVE, *USE, and *FLOAD, while the Atom provided *CAT, *LOAD,
 *SAVE, *RUN, *MON, *NOMON, *FLOAD, and *DOS. Their CLIs each provide the dot abbreviation
 and have similar command-parsing code. But the System 2 and Atom monitors lack the ability
 to EXEC scripts, and have no comment notation. *FLOAD, "finish loading", allowed part of
 a file to be loaded into memory by ignoring its block numbers -- an ability which was,
 perhaps wisely, dropped from the BBC.
.starCommandTable = $df10
    !text "."                                           command name
    !be16 .passToCurrentFilingSystem                    address of routine to call
    !byte 5                                             parameter to routine (in A)

    !text "FX"
    !be16 .fxEntryPoint
    !byte $FF

    !text "BASIC"
    !be16 .basicEntryPoint
    !byte 0

    !text "CAT"
    !be16 .passToCurrentFilingSystem
    !byte 5

    !text "CODE"
    !be16 .starCommandsForSpecificOSBYTEs
    !byte $88

    !text "EXEC"
    !be16 .closeAndOptionallyOpenANewEXECFile
    !byte 0

    !text "HELP"
    !be16 .starHelp
    !byte $FF

    !text "KEY"
    !be16 .starKey
    !byte $FF

    !text "LOAD"
    !be16 .starLoad
    !byte 0

    !text "LINE"
    !be16 .uservJumper
    !byte 1

    !text "MOTOR"
    !be16 .starCommandsForSpecificOSBYTEs
    !byte $89

    !text "OPT"
    !be16 .starCommandsForSpecificOSBYTEs
    !byte $8B

    !text "RUN"
    !be16 .passToCurrentFilingSystem
    !byte 4

    !text "ROM"
    !be16 .starCommandsForSpecificOSBYTEs
    !byte $8D

    !text "SAVE"
    !be16 .starLoadSave
    !byte 0

    !text "SPOOL"
    !be16 .starSpool
    !byte 0

    !text "TAPE"
    !be16 .starCommandsForSpecificOSBYTEs
    !byte $8C

    !text "TV"
    !be16 .starCommandsForSpecificOSBYTEs
    !byte $90

    !text ""
    !be16 .passToCurrentFilingSystem
    !byte 3
    !byte 0

§5. OSCLI - Command Line Interpreter.

       the default handler for CLIV vector

 On Entry:
       XY is the address of the command line (terminated with a carriage return)

 On Exit:
       A,X,Y,Flags undefined
.oscliEntryPoint = $df89
    STX .stringInputBufferAddressLow                    Store XY in
                                                        .stringInputBufferAddressLow/High
    STY .stringInputBufferAddressHigh                   
    LDA #8                                              
    JSR .passToCurrentFilingSystem                      Inform filing system CLI being
                                                        processed
    LDY #0                                              loop counter
-
    LDA (.stringInputBufferAddressLow),Y                Check the line is correctly
                                                        terminated
    CMP #.charRETURN                                    loop until CR is found
    BEQ .stringOK                                       
    INY                                                 move to next character
    BNE -                                               loop back if less than 256 bytes long
    RTS                                                 string is > 255 characters, so return

    String is terminated ok - now skip prepended spaces and '*'s
.stringOK = $df9e
    LDY #$FF                                            Y = offset into string (which is
                                                        about to be incremented so it will
                                                        start at zero)
-
    JSR .incAndSkipSpaces                               Skip any spaces
    BEQ .exit15                                         if (CR found) then branch (return)
    CMP #.charSTAR                                      check for '*'
    BEQ -                                               if ('*' found) then branch (Loop
                                                        back to skip it and skip spaces
                                                        again)

    JSR .skipSpacesAndCheckForCRInStringInput           doesn't need to move forward, but
                                                        checks if the current character is CR
    BEQ .exit15                                         if (CR found) then branch (exit)
    CMP #.charBAR                                       check for '|' (a comment)
    BEQ .exit15                                         if ('|' found) then branch (exit)
    CMP #.charFORWARDSLASH                              check for '/'
    BNE .startLookingForCommand                         if (NOT forward slash) then branch
                                                        forward
    INY                                                 Move past the '/'
    JSR .convertStringInputPointerToXY                  Convert the address
                                                        (.stringInputBufferAddressLow/High +
                                                        Y) into XY
    LDA #2                                              2 = execute "*/" command (See NAUG
                                                        Section 16.1.7 - FSCV, Page 256-7)
    BNE .passToCurrentFilingSystem                      ALWAYS branch (call into current
                                                        filing system service routine)

§6. Start looking for command.

 Look command up in command table
.startLookingForCommand = $dfbe
    STY .currentStringPointer                           Store offset to start of command in
                                                        command line
    LDX #0                                              start of command
    BEQ .lookForCommand                                 ALWAYS branch

§7. Compare character in A in a star command.

.compareCharacterInCommand = $dfc4
    EOR .starCommandTable,X                             compare with character from star
                                                        command table
    AND #%11011111                                      ignore case (zero the bit that
                                                        distinguishes between upper and
                                                        lower case)
    BNE .namesDontMatch                                 if (no match) then branch
    INY                                                 increment offset in command line
    CLC                                                 test next character

.atEndOfCommandName = $dfcd
    BCS .foundMatchForCommand                           
    INX                                                 increment offset in star command
                                                        table
    LDA (.stringInputBufferAddressLow),Y                read next character in command line
    JSR .isLetter                                       carry clear if letter in A
    BCC .compareCharacterInCommand                      

.lookForCommand = $dfd7
    LDA .starCommandTable,X                             read byte from command table
    BMI .endOfCommandStringFound                        if (top bit set, i.e. end of command
                                                        name string) then branch
    LDA (.stringInputBufferAddressLow),Y                read byte from command line
    CMP #.charDOT                                       check for '.'
    BEQ .foundDot                                       if (dot character found) then branch

.namesDontMatch = $dfe2
    CLC                                                 
    LDY .currentStringPointer                           
    DEY                                                 

.foundDot = $dfe6
    INY                                                 
    INX                                                 

-
    INX                                                 
    LDA .starCommandTable-2,X                           
    BEQ .passCommandToROMs                              reached end of table; unrecognised
                                                        star command
    BPL -                                               if (top bit clear, i.e. not yet end
                                                        of command name) then branch back
                                                        (move to next character)
    BMI .atEndOfCommandName                             ALWAYS branch (loop back)

§8. endOfCommandStringFound.

.endOfCommandStringFound = $dff2
    INX                                                 
    INX                                                 

.foundMatchForCommand = $dff4
    DEX                                                 
    DEX                                                 
    PHA                                                 push star command address (high byte)
    LDA .starCommandTable+1,X                            get star command address (low byte)
    PHA                                                 push star command address (low byte)
    JSR .skipSpacesAndCheckForCRInStringInput           skip over the following spaces
    CLC                                                 clear carry
    PHP                                                 push flags
    JSR .prepareRegistersForStarCommand                 prepare the A,X,Y registers for the
                                                        call to star command routine

    the RTI instruction restores the flags, and jumps to the address we've just put on
    the stack. (i.e. jumps to the star command routine.)
    RTI                                                 jump to star command routine

§9. prepareRegistersForStarCommand.

.prepareRegistersForStarCommand = $e004
    LDA .starCommandTable+2,X                           Get A parameter from table
    BMI .exit15                                         if (top bit set, this implies the
                                                        command has no string parameter, but
                                                        there may be numeric parameters)
                                                        then branch (exit, we are done)
                                                        otherwise we set XY to be the rest
                                                        of the command line (i.e. string
                                                        based)
    fall through...

§10. Convert string input pointer to XY.

 On Entry:
       X is the offset into the starCommandTable at the end of the string
       Y is the offset into the string at .stringInputBufferAddressLow/High

 On Exit:
       XY = (.stringInputBufferAddressLow/High + A)
       A = parameter from star command table
.convertStringInputPointerToXY = $e009
    TYA                                                 Pass Y line offset to A for later
    LDY .starCommandTable+2,X                           Get A parameter from table
    fall through...

§11. Convert string address to XY.

 On Entry:
       A = is offset into string at .stringInputBufferAddressLow/High

 On Exit:
       XY = .stringInputBufferAddressLow/High + A
       A = original value of Y on entry
.convertStringAddressToXY = $e00d
    CLC                                                 
    ADC .stringInputBufferAddressLow                    
    TAX                                                 low byte in X
    TYA                                                 remember original Y into A
    LDY .stringInputBufferAddressHigh                   }
    BCC .exit15                                         } put high byte in Y, increment as
                                                        } needed
    INY                                                 }
.exit15 = $e017
    RTS                                                 

§12. *BASIC.

 This command enters the BASIC language environment. The BASIC ROM is unique among Paged ROMs
 in not having a service entry. If it did, then the *BASIC command could be implemented in
 the BASIC ROM instead of here.
.basicEntryPoint = $e018
    LDX .basicROMNumber                                 Get BASIC ROM number
    BMI .passCommandToROMs                              if (no BASIC found) then branch
                                                        (pass command on to ROMs)
    SEC                                                 Set Carry = not entering from RESET
    JMP .osbyte142EntryPoint                            Enter language ROM in X

§13. Pass command to ROMs.

 Pass command on to Paged ROMs and to filing system
.passCommandToROMs = $e021
    LDY .currentStringPointer                           Restore pointer to start of command
    LDX #.romServiceCallUnrecognisedCommand             4 = Unrecognised star command
    JSR .osbyte143EntryPoint                            Pass to sideways ROMs
    BEQ .exit15                                         if (claimed by ROM) then branch
                                                        (exit)
    LDA .currentStringPointer                           Restore pointer to start of command
    JSR .convertStringAddressToXY                       Convert
                                                        .stringInputBufferAddressLow/High+A
                                                        to XY, ignore returned A
    LDA #3                                              3 = Auto-boot call

.passToCurrentFilingSystem = $e031
    JMP (.vectorFSCV)                                   call into current filing system
                                                        handler (for default handler,
                                                        see .fscEntryPoint)

§14. OSBYTE 139 - Select file options (*OPT).

 See .starOptEntryPoint for parameters

 On Entry:
       X,Y hold the *OPT parameters
.osbyte139EntryPoint = $e034
    ASL                                                 Double A (clear bit zero)
    fall through...

§15. OSBYTE 127 - Check for EOF on open file.

 On Entry:
       X = file handle
.osbyte127EntryPoint = $e035
    AND #1                                              A = 1 if entry via OSBYTE 127
                                                        A = 0 if entry via OSBYTE 139
    BPL .passToCurrentFilingSystem                      ALWAYS branch (send *OPT command
                                                        to the current filing system).

§16. Skip spaces.

 On Entry:
       .stringInputBufferAddressLow/High is address of string to test
       Y is the current offset within the string

 On Exit:
       Y is incremented to the next non-space character
       Z set if RETURN found (end of string)
.incAndSkipSpaces = $e039
    INY                                                 move to next character in string
.skipSpacesAndCheckForCRInStringInput = $e03a
    LDA (.stringInputBufferAddressLow),Y                read character from string
    CMP #.charSPACE                                     check for SPACE character
    BEQ .incAndSkipSpaces                               if (SPACE) then branch (loop back)
.compareWithReturnAndExit = $e040
    CMP #.charRETURN                                    set Z flag if charRETURN found
    RTS                                                 

§17. Skip spaces and comma.

 Skip past any spaces, and check for CR

 On Entry:
     if carry is clear, then skip SPACEs then look for RETURN
     if carry is set, then skip SPACEs then skip a comma or look for a RETURN

 On Exit:
       Y incremented past any spaces and comma
       Z set if RETURN found
.skipSpacesAndComma = $e043
    BCC .skipSpacesAndCheckForCRInStringInput           if (carry clear) then branch
.skipSpacesAndCommaValid = $e045
    JSR .skipSpacesAndCheckForCRInStringInput           
    CMP #.charCOMMA                                     
    BNE .compareWithReturnAndExit                       
    INY                                                 move to next character. Y is not
                                                        zero so Z is set.
    RTS                                                 

§18. Read a number (0-255) from string.

 On Entry:
       stringInputBufferAddressLow/High + Y is address of string holding a number
 On Exit:
       X = result (value 0-255) also stored in .tempWorkspaceE6
       Carry clear on error
       Zero set if RETURN found
.parseDecimalNumberFromString = $e04e
    JSR .skipSpacesAndCheckForCRInStringInput           skip spaces
    JSR .readDigitFromString                            read digit
    BCC .errorReadingString                             if (error found) then branch (clear
                                                        carry and exit)

.readingDigits = $e056
    STA .tempWorkspaceE6                                store temporary result
    JSR .readNextDigitFromString                        read next digit 0-9
    BCC .finishedParsingDigits                          if (not a digit) then branch (we
                                                        have finished)
    TAX                                                 remember best valid result so far
    LDA .tempWorkspaceE6                                recall previous digit P
    ASL                                                 A=P*2
    BCS .errorReadingString                             result too big, return with carry
                                                        clear (error)
    ASL                                                 A=P*4
    BCS .errorReadingString                             result too big, return with carry
                                                        clear (error)
    ADC .tempWorkspaceE6                                A=(P*4)+P =P*5
    BCS .errorReadingString                             result too big, return with carry
                                                        clear (error)
    ASL                                                 A=P*10
    BCS .errorReadingString                             result too big, return with carry
                                                        clear (error)
    STA .tempWorkspaceE6                                Store temporary result
    TXA                                                 recall current digit
    ADC .tempWorkspaceE6                                Add previous digit times ten
    BCS .errorReadingString                             result too big, return with carry
                                                        clear (error)
    BCC .readingDigits                                  ALWAYS branch back

§19. finishedParsingDigits.

.finishedParsingDigits = $e076
    LDX .tempWorkspaceE6                                get best valid result
    CMP #.charRETURN                                    set Z flag if RETURN found
    SEC                                                 set carry (success flag)
    RTS                                                 

§20. readNextDigitFromString.

.readNextDigitFromString = $e07c
    INY
.readDigitFromString = $e07d
    LDA (.stringInputBufferAddressLow),Y                get digit from string
    CMP #.charNINE + 1                                  }
    BCS .errorReadingString                             } check if digit is in range '0' to
                                                        } '9'
    CMP #.charZERO                                      }
    BCC .errorReadingString                             }
    AND #$0F                                            mask off bits to leave binary value
                                                        0-9
    RTS                                                 

§21. notAHexByte.

.notAHexByte = $e08a
    JSR .skipSpacesAndCommaValid                        

.errorReadingString = $e08d
    CLC                                                 
    RTS                                                 

§22. Read hex digit.

 On Exit:
       on success, carry is set, A = 0-15, based on ascii digit in string (0-9,A-F)
       on error, carry clear
.readHexDigit = $e08f
    JSR .readDigitFromString                            read digit 0-9
    BCS +                                               if (digit not in 0-9 range) then
                                                        branch
    AND #%11011111                                      clear bit 5 (converts lower case
                                                        letters to upper case)
    CMP #.charF + 1                                     
    BCS .notAHexByte                                    if (higher than 'F') then branch
                                                        (exit with error)
    CMP #.charA                                         
    BCC .notAHexByte                                    if (lower than 'A') then branch
                                                        (exit with error)
    PHP                                                 carry set, push flags
    SBC #.charA - 10                                    Convert to range 10-15
    PLP                                                 restore flags (carry set, for
                                                        successful result)
+
    INY                                                 move on to next byte
    RTS