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
- §2. OSRDCH - Read a character
- §3. Copyright string
- §4. Star command table
- §5. OSCLI - Command Line Interpreter
- §6. Start looking for command
- §7. Compare character in A in a star command
- §8. endOfCommandStringFound
- §9. prepareRegistersForStarCommand
- §10. Convert string input pointer to XY
- §11. Convert string address to XY
- §12. *BASIC
- §13. Pass command to ROMs
- §14. OSBYTE 139 - Select file options (*OPT)
- §15. OSBYTE 127 - Check for EOF on open file
- §16. Skip spaces
- §17. Skip spaces and comma
- §18. Read a number (0-255) from string
- §19. finishedParsingDigits
- §20. readNextDigitFromString
- §21. notAHexByte
- §22. Read hex digit
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
Only used as part of the check for valid Paged ROM, never printed
.copyrightString = $df0c !text ")C(",0 Copyright string (backwards)
*. <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)
.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
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
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).
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
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
.finishedParsingDigits = $e076 LDX .tempWorkspaceE6 get best valid result CMP #.charRETURN set Z flag if RETURN found SEC set carry (success flag) RTS
.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
.notAHexByte = $e08a JSR .skipSpacesAndCommaValid .errorReadingString = $e08d CLC RTS
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