389 lines
8.6 KiB
NASM
389 lines
8.6 KiB
NASM
; console.asm
|
|
; Talking to the console.
|
|
;
|
|
; This is the modern replacement for print.asm, which was written for a machine with one
|
|
; Data Pointer and no vector table. The old one is left where it is, because the programs
|
|
; that include it still work and are meant to keep working.
|
|
;
|
|
; Two things are different here, and both are deliberate.
|
|
;
|
|
; There is no branch at the top. print.asm begins with BRI start, so that a program
|
|
; including it arrives at its own entry point instead of falling into the library. That
|
|
; was the only way to do it before the Boot vector existed. A program including this file
|
|
; says where it begins in its own Vector Segment:
|
|
;
|
|
; #Vectors
|
|
; Boot start
|
|
;
|
|
; And every routine here names the Data Pointer it works through rather than assuming the
|
|
; only one. A pointer handed in is DP0. Nothing here disturbs what the caller left in
|
|
; DP3, which is the one a return survives in.
|
|
;
|
|
; What a routine gives back is in Q, because Q and DP3 are the only things a RET does not
|
|
; put back the way it found them.
|
|
;
|
|
; Written by Anachronaut
|
|
|
|
#Program
|
|
|
|
; ---- Characters and strings ----
|
|
|
|
; A line feed.
|
|
newLine:
|
|
INIA 0x0A
|
|
OUTA 0x00
|
|
RET
|
|
|
|
; DP0 names a string ending in a zero byte. Prints it.
|
|
printString:
|
|
LDA.0
|
|
BRA printStringDone
|
|
OUTA 0x00
|
|
INCD.0
|
|
BRI printString
|
|
printStringDone:
|
|
RET
|
|
|
|
; A holds how many spaces to print. None is a fair answer, and prints nothing.
|
|
printSpaces:
|
|
BRA printSpacesDone
|
|
INIB 0x20
|
|
printSpacesLoop:
|
|
OUTB 0x00
|
|
DECA
|
|
BNA printSpacesLoop
|
|
printSpacesDone:
|
|
RET
|
|
|
|
; ---- Hexadecimal ----
|
|
|
|
; A holds a byte. Prints it as two hexadecimal digits, high one first.
|
|
;
|
|
; A and B are a circular shift register sixteen bits long, so rotating right four times
|
|
; with B empty walks the high nybble down into place and parks the low one in B. The call
|
|
; between the two halves puts A and B back as they were, which is what lets the second
|
|
; rotation find the low nybble still waiting.
|
|
printByteHex:
|
|
RSTB
|
|
SHR SHR SHR SHR
|
|
CALL printHexDigit
|
|
RSTA
|
|
SHL SHL SHL SHL
|
|
CALL printHexDigit
|
|
RET
|
|
|
|
; DP0 names two bytes, most significant first, the way every number on a SplitBit disk is
|
|
; stored. Prints them as four hexadecimal digits.
|
|
printWordHex:
|
|
LDA.0
|
|
CALL printByteHex
|
|
INCD.0
|
|
LDA.0
|
|
CALL printByteHex
|
|
RET
|
|
|
|
; A holds a nybble. Prints the one character that stands for it.
|
|
printHexDigit:
|
|
INIB 0d10
|
|
CCF
|
|
SUB
|
|
BRC printDecimalDigit ; Under ten, so it is a plain digit.
|
|
INIB 0x37 ; 'A' is ten, so this is the offset that gets there.
|
|
CCF
|
|
ADD
|
|
OUTQ 0x00
|
|
RET
|
|
|
|
; A holds a digit from zero to nine. Prints it.
|
|
printDecimalDigit:
|
|
INIB 0x30
|
|
CCF
|
|
ADD
|
|
OUTQ 0x00
|
|
RET
|
|
|
|
; ---- Decimal ----
|
|
|
|
; A holds a byte. Prints it in decimal, without leading zeroes.
|
|
printByteDecimal:
|
|
SETD.0 ConsoleValue
|
|
STA.0
|
|
SETD.1 ConsoleLeading
|
|
RSTA
|
|
STA.1 ; Nothing has been printed yet.
|
|
|
|
INIA 0d100
|
|
CALL printBytePlace
|
|
INIA 0d10
|
|
CALL printBytePlace
|
|
|
|
; Whatever is left is the ones, and it prints whether or not it is a zero, because a
|
|
; number has to show at least one digit.
|
|
SETD.0 ConsoleValue
|
|
LDA.0
|
|
CALL printDecimalDigit
|
|
RET
|
|
|
|
; A holds a power of ten. Counts how many times it comes out of ConsoleValue, prints that
|
|
; as a digit, and leaves the remainder behind. A leading zero prints nothing.
|
|
printBytePlace:
|
|
SETD.2 ConsoleBytePower
|
|
STA.2
|
|
SETD.0 ConsoleValue
|
|
SETD.1 ConsoleCount
|
|
RSTA
|
|
STA.1
|
|
printBytePlaceLoop:
|
|
LDA.0
|
|
LDB.2
|
|
CCF
|
|
SUB
|
|
BRC printBytePlaceDone ; It went below zero, so it does not come out again.
|
|
STQ.0
|
|
LDA.1
|
|
INCA
|
|
STA.1
|
|
BRI printBytePlaceLoop
|
|
printBytePlaceDone:
|
|
LDA.1
|
|
BNA printBytePlaceShow
|
|
; The digit is a zero, which only prints if something has been printed before it.
|
|
SETD.2 ConsoleLeading
|
|
LDA.2
|
|
BRA printBytePlaceQuiet
|
|
RSTA
|
|
printBytePlaceShow:
|
|
CALL printDecimalDigit
|
|
SETD.2 ConsoleLeading
|
|
INIA 0x01
|
|
STA.2
|
|
printBytePlaceQuiet:
|
|
RET
|
|
|
|
; DP0 names a two byte number, most significant byte first. Prints it in decimal, without
|
|
; leading zeroes. Sixty five thousand five hundred and thirty five is the largest thing it
|
|
; can be handed, which is the whole of an address, so nothing overflows this.
|
|
printWordDecimal:
|
|
SETD.1 ConsoleWord
|
|
CALL consoleCopyWord
|
|
SETD.1 ConsoleLeading
|
|
RSTA
|
|
STA.1
|
|
|
|
SETD.0 ConsoleTenThousand
|
|
CALL printWordPlace
|
|
SETD.0 ConsoleThousand
|
|
CALL printWordPlace
|
|
SETD.0 ConsoleHundred
|
|
CALL printWordPlace
|
|
SETD.0 ConsoleTen
|
|
CALL printWordPlace
|
|
|
|
; What is left is under ten, so it is in the low byte and it is the last digit.
|
|
SETD.0 ConsoleWord
|
|
INCD.0
|
|
LDA.0
|
|
CALL printDecimalDigit
|
|
RET
|
|
|
|
; DP0 names a power of ten, two bytes of it. The same counting as printBytePlace, done
|
|
; sixteen bits wide.
|
|
printWordPlace:
|
|
SETD.1 ConsolePower
|
|
CALL consoleCopyWord
|
|
SETD.1 ConsoleCount
|
|
RSTA
|
|
STA.1
|
|
printWordPlaceLoop:
|
|
CALL consoleTakePower
|
|
BNQ printWordPlaceDone
|
|
SETD.1 ConsoleCount
|
|
LDA.1
|
|
INCA
|
|
STA.1
|
|
BRI printWordPlaceLoop
|
|
printWordPlaceDone:
|
|
SETD.1 ConsoleCount
|
|
LDA.1
|
|
BNA printWordPlaceShow
|
|
SETD.1 ConsoleLeading
|
|
LDA.1
|
|
BRA printWordPlaceQuiet
|
|
RSTA
|
|
printWordPlaceShow:
|
|
CALL printDecimalDigit
|
|
SETD.1 ConsoleLeading
|
|
INIA 0x01
|
|
STA.1
|
|
printWordPlaceQuiet:
|
|
RET
|
|
|
|
; Takes ConsolePower out of ConsoleWord, if it comes out at all. Q is zero if it did, and
|
|
; then ConsoleWord is the smaller for it. If it did not, ConsoleWord is left alone.
|
|
;
|
|
; The subtraction is done into a spare word rather than in place, because whether it fits
|
|
; is not known until the high half is done, and by then an in place low half would already
|
|
; have been spent.
|
|
;
|
|
; The low half clears the Carry Flag first and the high half does not: the borrow the low
|
|
; half leaves behind is exactly what the high half has to subtract as well. Nothing
|
|
; between them disturbs it, since only the arithmetic instructions and CCF touch it.
|
|
consoleTakePower:
|
|
SETD.0 ConsoleWord
|
|
INCD.0
|
|
SETD.1 ConsolePower
|
|
INCD.1
|
|
LDA.0
|
|
LDB.1
|
|
CCF
|
|
SUB
|
|
SETD.2 ConsoleSpare
|
|
INCD.2
|
|
STQ.2
|
|
|
|
SETD.0 ConsoleWord
|
|
SETD.1 ConsolePower
|
|
LDA.0
|
|
LDB.1
|
|
SUB
|
|
BRC consoleTakeNothing
|
|
SETD.2 ConsoleSpare
|
|
STQ.2
|
|
|
|
SETD.0 ConsoleSpare
|
|
SETD.1 ConsoleWord
|
|
CALL consoleCopyWord
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD ; Q is zero: it came out.
|
|
RET
|
|
consoleTakeNothing:
|
|
RSTA
|
|
INIB 0x01
|
|
CCF
|
|
ADD ; Q is one: it did not.
|
|
RET
|
|
|
|
; Two bytes from DP0 to DP1, most significant first.
|
|
consoleCopyWord:
|
|
LDA.0
|
|
STA.1
|
|
INCD.0
|
|
INCD.1
|
|
LDA.0
|
|
STA.1
|
|
RET
|
|
|
|
; ---- Reading ----
|
|
|
|
; DP0 names a buffer and B says how many characters it holds, not counting the zero byte
|
|
; that ends it. Reads a line from the console into it. Q is how long the line turned out
|
|
; to be.
|
|
;
|
|
; A line longer than the buffer is cut short, and the rest of it is read and thrown away
|
|
; rather than left to turn up as the next line.
|
|
;
|
|
; ConsoleEndOfInput is set if the console ran out instead of ending a line. That is a
|
|
; different thing from an empty line, and a program that reads until there is no more has
|
|
; to be able to tell them apart.
|
|
readLine:
|
|
SETD.1 ConsoleRoom
|
|
STB.1
|
|
SETD.1 ConsoleLength
|
|
RSTA
|
|
STA.1
|
|
SETD.1 ConsoleEndOfInput
|
|
STA.1
|
|
readLineNext:
|
|
INA 0x00
|
|
INIB 0x0A
|
|
CCF
|
|
SUB
|
|
BRQ readLineDone ; A line feed ends the line. A is still the character.
|
|
INIB 0xFF
|
|
CCF
|
|
SUB
|
|
BRQ readLineEnd ; There is no more to be had.
|
|
|
|
; Is there room for it? A still holds the character, so it is put somewhere safe while
|
|
; the counting is done.
|
|
SETD.1 ConsoleChar
|
|
STA.1
|
|
SETD.1 ConsoleLength
|
|
LDA.1
|
|
SETD.1 ConsoleRoom
|
|
LDB.1
|
|
CCF
|
|
SUB
|
|
BRQ readLineNext ; Full. Read on, and drop what comes.
|
|
|
|
SETD.1 ConsoleChar
|
|
LDA.1
|
|
STA.0
|
|
INCD.0
|
|
SETD.1 ConsoleLength
|
|
LDA.1
|
|
INCA
|
|
STA.1
|
|
BRI readLineNext
|
|
readLineEnd:
|
|
SETD.1 ConsoleEndOfInput
|
|
INIA 0x01
|
|
STA.1
|
|
readLineDone:
|
|
RSTA
|
|
STA.0 ; The zero byte that ends it.
|
|
SETD.1 ConsoleLength
|
|
LDA.1
|
|
RSTB
|
|
CCF
|
|
ADD ; Q is how long the line is.
|
|
RET
|
|
|
|
#Data
|
|
|
|
; ---- What readLine keeps while it works ----
|
|
|
|
ConsoleRoom:
|
|
0x00
|
|
ConsoleLength:
|
|
0x00
|
|
ConsoleChar:
|
|
0x00
|
|
|
|
; Set when the console ran out rather than ending a line. Cleared at the start of every
|
|
; readLine, so it always describes the last line read.
|
|
ConsoleEndOfInput:
|
|
0x00
|
|
|
|
; ---- What the number routines keep while they work ----
|
|
|
|
; Whether any digit has been printed yet, which is what decides if a zero is a leading
|
|
; one or a real one.
|
|
ConsoleLeading:
|
|
0x00
|
|
ConsoleCount:
|
|
0x00
|
|
ConsoleValue:
|
|
0x00
|
|
ConsoleBytePower:
|
|
0x00
|
|
ConsolePower:
|
|
0x00 0x00
|
|
ConsoleWord:
|
|
0x00 0x00
|
|
ConsoleSpare:
|
|
0x00 0x00
|
|
|
|
; The powers of ten, written the way every number here is written: most significant byte
|
|
; first.
|
|
ConsoleTenThousand:
|
|
0x27 0x10
|
|
ConsoleThousand:
|
|
0x03 0xE8
|
|
ConsoleHundred:
|
|
0x00 0x64
|
|
ConsoleTen:
|
|
0x00 0x0A
|