307 lines
6.5 KiB
NASM
307 lines
6.5 KiB
NASM
; text.asm
|
|
; Picking a line of typing apart.
|
|
;
|
|
; A shell reads a line and has to decide what was asked for. That is two jobs: cutting the
|
|
; first word off the line, and telling whether a word is the one being looked for. There
|
|
; is nothing else here, because there is nothing else a command line needs yet.
|
|
;
|
|
; Written by Anachronaut
|
|
|
|
#Program
|
|
|
|
; DP0 names a line ending in a zero byte. Cuts the first word off it, in place, by writing
|
|
; a zero byte over the space that ends the word. DP0 is unchanged, because a RET puts it
|
|
; back, so afterwards DP0 names just the first word.
|
|
;
|
|
; Where the rest of the line begins goes in TextRest, with any spaces between skipped. A
|
|
; line with only one word on it leaves TextRest naming that line's zero byte, which reads
|
|
; as an empty argument rather than as a missing one, and is the same thing here.
|
|
textSplit:
|
|
LDA.0
|
|
BRA textSplitHere ; The line ended, so the whole of it was one word.
|
|
INIB 0x20
|
|
CCF
|
|
SUB
|
|
BRQ textSplitCut
|
|
INCD.0
|
|
BRI textSplit
|
|
|
|
textSplitCut:
|
|
RSTA
|
|
STA.0 ; The space becomes the end of the word.
|
|
INCD.0
|
|
|
|
textSplitSkip:
|
|
LDA.0
|
|
BRA textSplitHere
|
|
INIB 0x20
|
|
CCF
|
|
SUB
|
|
BNQ textSplitHere ; Something that is not a space: the rest starts here.
|
|
INCD.0
|
|
BRI textSplitSkip
|
|
|
|
textSplitHere:
|
|
SETD.1 TextRest
|
|
STD.0.1
|
|
RET
|
|
|
|
; DP0 and DP1 name strings ending in zero bytes. Q is zero if they are the same.
|
|
;
|
|
; The two ending together is what makes them the same. Comparing until one of them ends
|
|
; would call "dir" and "dirty" the same word, which is the kind of thing a shell gets
|
|
; wrong once and confusingly.
|
|
textSame:
|
|
LDA.0
|
|
LDB.1
|
|
CCF
|
|
SUB
|
|
BNQ textDiffer
|
|
LDA.0
|
|
BRA textAlike ; Equal, and both of them zero: they ended together.
|
|
INCD.0
|
|
INCD.1
|
|
BRI textSame
|
|
|
|
textAlike:
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD ; Q is zero: the same.
|
|
RET
|
|
|
|
textDiffer:
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD ; Q is one: not the same.
|
|
RET
|
|
|
|
; DP0 names text. Reads hexadecimal digits off the front of it into TextValue, most
|
|
; significant byte first. Q is zero if there was at least one digit to read.
|
|
;
|
|
; Digits past the fourth push the earlier ones off the top rather than being refused,
|
|
; which is what typing over an address does on every monitor there has ever been.
|
|
textHexWord:
|
|
RSTA
|
|
SETD.1 TextValue
|
|
STA.1
|
|
INCD.1
|
|
STA.1
|
|
SETD.1 TextDigits
|
|
STA.1
|
|
|
|
textHexLoop:
|
|
LDA.0
|
|
CALL textHexDigit
|
|
PSHQ
|
|
POPA
|
|
INIB 0xFF
|
|
CCF
|
|
SUB
|
|
BRQ textHexEnd ; Not a digit, so the number stopped before it.
|
|
|
|
CALL textHexShift
|
|
SETD.1 TextDigits
|
|
LDA.1
|
|
INCA
|
|
STA.1
|
|
INCD.0
|
|
BRI textHexLoop
|
|
|
|
textHexEnd:
|
|
SETD.1 TextDigits
|
|
LDA.1
|
|
BRA textHexNothing
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD ; Q is zero: there was a number.
|
|
RET
|
|
textHexNothing:
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD ; Q is one: there was not.
|
|
RET
|
|
|
|
; A holds the digit just read. Moves TextValue up by one place and puts the digit in the
|
|
; hole that leaves.
|
|
;
|
|
; A and B are a circular shift register sixteen bits long, so rotating them left four
|
|
; times multiplies the pair by sixteen. What fell off the top of the high byte comes round
|
|
; into the bottom of the low one, which is exactly the nybble the new digit wants, so it
|
|
; is masked away first.
|
|
textHexShift:
|
|
SETD.1 TextDigit
|
|
STA.1
|
|
|
|
SETD.0 TextValue
|
|
LDA.0
|
|
INCD.0
|
|
LDB.0
|
|
SHL SHL SHL SHL
|
|
|
|
SETD.0 TextValue
|
|
STA.0 ; The high byte is finished.
|
|
|
|
PSHB
|
|
POPA
|
|
INIB 0xF0
|
|
AND
|
|
MVQA
|
|
SETD.1 TextDigit
|
|
LDB.1
|
|
OR
|
|
SETD.0 TextValue
|
|
INCD.0
|
|
STQ.0
|
|
RET
|
|
|
|
; A holds a character. Q is what it is worth as a hexadecimal digit, or 0xFF if it is not
|
|
; one. Upper and lower case both count, because nobody wants to be told which they meant.
|
|
;
|
|
; Everything below works from the distance above '0', which is why the letters are tested
|
|
; at seventeen and thirty two rather than at anything recognisable.
|
|
textHexDigit:
|
|
INIB 0x30
|
|
CCF
|
|
SUB
|
|
BRC textHexNo ; Below '0'.
|
|
MVQA
|
|
INIB 0d10
|
|
CCF
|
|
SUB
|
|
BNC textHexUpper ; Ten or more above '0', so not 0 to 9.
|
|
RSTB
|
|
CCF
|
|
ADD ; Q is the digit itself.
|
|
RET
|
|
|
|
textHexUpper:
|
|
INIB 0d17
|
|
CCF
|
|
SUB
|
|
BRC textHexNo ; Between '9' and 'A'.
|
|
MVQA
|
|
INIB 0d6
|
|
CCF
|
|
SUB
|
|
BNC textHexLower ; Past 'F'.
|
|
INIB 0d10
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
textHexLower:
|
|
INIB 0d32
|
|
CCF
|
|
SUB
|
|
BRC textHexNo ; Between 'F' and 'a'.
|
|
MVQA
|
|
INIB 0d6
|
|
CCF
|
|
SUB
|
|
BNC textHexNo ; Past 'f'.
|
|
INIB 0d10
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
textHexNo:
|
|
RSTA
|
|
INIB 0xFF
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; ---- A number written in decimal ----
|
|
;
|
|
; DP0 names it. Q is the value, and TextDigits says how many digits were read, which is
|
|
; zero when there was no number there at all. Stops at the first thing that is not a digit.
|
|
;
|
|
; Decimal rather than hex, and one byte rather than two, because this is for the numbers a
|
|
; person types at a program: a line number, a count, a how many. Nobody counts lines in
|
|
; hex, and nobody types a line number above 255 on a machine this size. textHexWord is
|
|
; still the one for an address, where hex is what everybody means.
|
|
;
|
|
; Ten times the running total is worked out as eight of it plus two of it, because nothing
|
|
; on this machine multiplies. Anything past 255 wraps, which is what the same sum does
|
|
; everywhere else here.
|
|
textNumber:
|
|
RSTA
|
|
SETD.1 TextValue
|
|
STA.1
|
|
SETD.1 TextDigits
|
|
STA.1
|
|
|
|
textNumberLoop:
|
|
LDA.0
|
|
BRA textNumberDone
|
|
|
|
; Below '0' or above '9' ends it.
|
|
INIB 0d48
|
|
CCF
|
|
SUB
|
|
BRC textNumberDone ; It borrowed, so the character was below '0'.
|
|
MVQA
|
|
INIB 0d10
|
|
CCF
|
|
SUB
|
|
BNC textNumberDone ; It did not borrow, so it was ten or more past '0'.
|
|
|
|
PSHA ; The digit, while the total is multiplied.
|
|
SETD.1 TextValue
|
|
LDA.1
|
|
LDB.1
|
|
CCF
|
|
ADD ; Twice.
|
|
MVQA
|
|
MVQB
|
|
PSHA ; Twice, kept: ten is eight and two.
|
|
CCF
|
|
ADD ; Four times.
|
|
MVQA
|
|
MVQB
|
|
CCF
|
|
ADD ; Eight times.
|
|
MVQA
|
|
POPB
|
|
CCF
|
|
ADD ; Ten times.
|
|
MVQA
|
|
POPB
|
|
CCF
|
|
ADD ; And the digit.
|
|
SETD.1 TextValue
|
|
STQ.1
|
|
|
|
SETD.1 TextDigits
|
|
LDA.1
|
|
INCA
|
|
STA.1
|
|
INCD.0
|
|
BRI textNumberLoop
|
|
|
|
textNumberDone:
|
|
SETD.1 TextValue
|
|
LDA.1
|
|
RSTB
|
|
CCF
|
|
ADD ; Q is the value, the way a routine hands a byte back.
|
|
RET
|
|
|
|
#Data
|
|
|
|
; Where the rest of the line begins, after textSplit has taken a word off the front.
|
|
TextRest:
|
|
0x00 0x00
|
|
|
|
; What textHexWord read, and what it needs while reading it.
|
|
TextValue:
|
|
0x00 0x00
|
|
TextDigits:
|
|
0x00
|
|
TextDigit:
|
|
0x00
|