Fixed assembler bug that caused crash on IR array resize. Added line editor app.
This commit is contained in:
@@ -0,0 +1,829 @@
|
||||
; Edit, a line editor for CosmOS.
|
||||
;
|
||||
; The first program on this machine that makes a file a person typed. Everything on every
|
||||
; disk before this one was put there by the host tool.
|
||||
;
|
||||
; It is line oriented, in the manner of ed, and that is a deliberate choice rather than a
|
||||
; limitation of the machine - Snake already draws a whole screen and steers with single
|
||||
; keys. A full screen editor wants scrolling, a redraw model and cursor arithmetic, none of
|
||||
; which teaches anything about files, and files are what this exists to exercise. So it
|
||||
; stays in line mode and reads whole lines, which is what the console does without being
|
||||
; asked for anything.
|
||||
;
|
||||
; l list the whole thing, numbered
|
||||
; a add lines at the end, until a line that is just a dot
|
||||
; i <n> put lines in before line n, the same way
|
||||
; c <n> change line n
|
||||
; d <n> delete line n
|
||||
; w write it back
|
||||
; q stop without writing
|
||||
;
|
||||
; ---- How the text is kept ----
|
||||
;
|
||||
; A LINKED LIST OF LINES, not one buffer with newlines in it. Each line is a node holding
|
||||
; where the next one is, how long it is, and its bytes:
|
||||
;
|
||||
; 0 2 where the next line is, or zero
|
||||
; 2 1 how many bytes this line has
|
||||
; 3 the bytes
|
||||
;
|
||||
; Inserting is then two pointers changed and nothing moved, and so is deleting. With one
|
||||
; flat buffer both of them would mean shifting everything after the edit, which on a
|
||||
; machine with no memcpy is a loop over every byte of the rest of the document, run for
|
||||
; every keystroke's worth of editing.
|
||||
;
|
||||
; The price is that DELETED LINES ARE NOT REUSED. A new line always goes at the end of the
|
||||
; arena, and an unlinked one just sits there. A session that edits heavily uses more room
|
||||
; than the document needs, and writing the file out and reading it back is what tidies it
|
||||
; up. That is an honest trade for a program this size, and it is written down here rather
|
||||
; than left as a surprise.
|
||||
;
|
||||
; Two regions are used by arrangement rather than reserved, because reserving them would
|
||||
; put tens of kilobytes of zeroes into the file for no reason:
|
||||
;
|
||||
; 0x4000 the file, on its way in or out
|
||||
; 0x8000 the arena the lines live in
|
||||
;
|
||||
; Nothing is running but this, so both are ours. It is the same arrangement CosmOS makes
|
||||
; with 0x8000 while it is loading something, for the same reason.
|
||||
|
||||
#Include services.asm
|
||||
|
||||
#Program
|
||||
|
||||
#Base 0x2000
|
||||
|
||||
start:
|
||||
SETD.0 FileName
|
||||
INIB 0d23
|
||||
SWI osArgument
|
||||
SETD.0 FileName
|
||||
LDA.0
|
||||
BRA noName
|
||||
|
||||
; Everything is set here rather than trusted to be zero, since running a program a second
|
||||
; time does not load it again.
|
||||
RSTA
|
||||
SETD.0 TextHead
|
||||
STA.0
|
||||
INCD.0
|
||||
STA.0
|
||||
SETD.0 ArenaFree
|
||||
INIA 0x80
|
||||
STA.0
|
||||
INCD.0
|
||||
RSTA
|
||||
STA.0
|
||||
|
||||
CALL sbfsMount
|
||||
BNQ noDisk
|
||||
|
||||
CALL loadFile
|
||||
|
||||
SETD.0 FileName
|
||||
CALL printString
|
||||
SETD.0 CommaText
|
||||
CALL printString
|
||||
CALL countLines
|
||||
MVQA
|
||||
CALL printByteDecimal
|
||||
SETD.0 LinesText
|
||||
CALL printString
|
||||
CALL newLine
|
||||
|
||||
commandLoop:
|
||||
SETD.0 PromptText
|
||||
CALL printString
|
||||
SETD.0 Command
|
||||
INIB 0d40
|
||||
CALL readLine
|
||||
|
||||
; Running out of typing ends it, the same way it ends the shell.
|
||||
SETD.0 ConsoleEndOfInput
|
||||
LDA.0
|
||||
BNA quit
|
||||
|
||||
SETD.0 Command
|
||||
LDA.0
|
||||
BRA commandLoop ; An empty line asks for nothing.
|
||||
|
||||
; Whatever number follows the letter, if there is one. The spaces between the two are
|
||||
; stepped over first: a number is what somebody typed after "d ", not after "d".
|
||||
SETD.0 Command
|
||||
INCD.0
|
||||
commandSpaces:
|
||||
LDA.0
|
||||
INIB 0x20
|
||||
XOR
|
||||
BNQ commandArgument
|
||||
INCD.0
|
||||
BRI commandSpaces
|
||||
commandArgument:
|
||||
CALL textNumber
|
||||
MVQA
|
||||
SETD.0 Wanted
|
||||
STA.0
|
||||
|
||||
SETD.0 Command
|
||||
LDA.0
|
||||
|
||||
INIB 0d108 ; l
|
||||
XOR
|
||||
BRQ doList
|
||||
INIB 0d97 ; a
|
||||
XOR
|
||||
BRQ doAppend
|
||||
INIB 0d105 ; i
|
||||
XOR
|
||||
BRQ doInsert
|
||||
INIB 0d99 ; c
|
||||
XOR
|
||||
BRQ doChange
|
||||
INIB 0d100 ; d
|
||||
XOR
|
||||
BRQ doDelete
|
||||
INIB 0d119 ; w
|
||||
XOR
|
||||
BRQ doWrite
|
||||
INIB 0d113 ; q
|
||||
XOR
|
||||
BRQ quit
|
||||
|
||||
SETD.0 WhatText
|
||||
CALL printString
|
||||
CALL newLine
|
||||
BRI commandLoop
|
||||
|
||||
quit:
|
||||
SWI osExit
|
||||
|
||||
noName:
|
||||
SETD.0 NoNameText
|
||||
CALL printString
|
||||
CALL newLine
|
||||
SWI osExit
|
||||
|
||||
noDisk:
|
||||
SETD.0 NoDiskText
|
||||
CALL printString
|
||||
CALL newLine
|
||||
SWI osExit
|
||||
|
||||
; ---- The commands ----
|
||||
|
||||
doList:
|
||||
CALL listLines
|
||||
BRI commandLoop
|
||||
|
||||
doAppend:
|
||||
CALL countLines
|
||||
MVQA
|
||||
INCA
|
||||
SETD.0 Wanted
|
||||
STA.0 ; Adding at the end is inserting before the line after it.
|
||||
BRI insertLoop
|
||||
|
||||
doInsert:
|
||||
SETD.0 Wanted
|
||||
LDA.0
|
||||
BRA insertNeedsLine
|
||||
insertLoop:
|
||||
SETD.0 EnteringText
|
||||
CALL printString
|
||||
SETD.0 Entry
|
||||
INIB 0d80
|
||||
CALL readLine
|
||||
SETD.0 ConsoleEndOfInput
|
||||
LDA.0
|
||||
BNA commandLoop
|
||||
|
||||
; A line that is just a dot ends it, which is the oldest convention there is for this.
|
||||
SETD.0 Entry
|
||||
SETD.1 DotText
|
||||
CALL textSame
|
||||
BRQ commandLoop
|
||||
|
||||
SETD.0 Entry
|
||||
CALL makeNode
|
||||
SETD.0 Wanted
|
||||
LDA.0
|
||||
CALL linkBefore
|
||||
SETD.0 Wanted
|
||||
LDA.0
|
||||
INCA
|
||||
STA.0 ; The next one goes after the one just put in.
|
||||
BRI insertLoop
|
||||
|
||||
insertNeedsLine:
|
||||
SETD.0 NeedsLineText
|
||||
CALL printString
|
||||
CALL newLine
|
||||
BRI commandLoop
|
||||
|
||||
doChange:
|
||||
SETD.0 Wanted
|
||||
LDA.0
|
||||
BRA insertNeedsLine
|
||||
CALL findLine
|
||||
BNQ noSuchLine
|
||||
|
||||
SETD.0 EnteringText
|
||||
CALL printString
|
||||
SETD.0 Entry
|
||||
INIB 0d80
|
||||
CALL readLine
|
||||
SETD.0 ConsoleEndOfInput
|
||||
LDA.0
|
||||
BNA commandLoop
|
||||
|
||||
SETD.0 Entry
|
||||
CALL makeNode
|
||||
SETD.0 Wanted
|
||||
LDA.0
|
||||
CALL linkBefore ; The new one goes in front of the old one,
|
||||
SETD.0 Wanted
|
||||
LDA.0
|
||||
INCA
|
||||
CALL unlinkLine ; and the old one, now one further along, comes out.
|
||||
BRI commandLoop
|
||||
|
||||
doDelete:
|
||||
SETD.0 Wanted
|
||||
LDA.0
|
||||
BRA insertNeedsLine
|
||||
CALL unlinkLine
|
||||
BNQ noSuchLine
|
||||
BRI commandLoop
|
||||
|
||||
noSuchLine:
|
||||
SETD.0 NoLineText
|
||||
CALL printString
|
||||
CALL newLine
|
||||
BRI commandLoop
|
||||
|
||||
doWrite:
|
||||
CALL writeFile
|
||||
BNQ writeFailed
|
||||
SETD.0 WrittenText
|
||||
CALL printString
|
||||
SETD.0 WroteSize
|
||||
CALL printWordDecimal
|
||||
SETD.0 BytesText
|
||||
CALL printString
|
||||
CALL newLine
|
||||
BRI commandLoop
|
||||
|
||||
writeFailed:
|
||||
SETD.0 NoWriteText
|
||||
CALL printString
|
||||
CALL newLine
|
||||
BRI commandLoop
|
||||
|
||||
; ---- The list of lines ----
|
||||
|
||||
; DP0 is a string. Puts a node holding it at the end of the arena, and leaves DP3 on it.
|
||||
makeNode:
|
||||
SETD.1 ArenaFree
|
||||
LDD.3.1
|
||||
|
||||
PSHD.3
|
||||
POPD.1
|
||||
RSTA
|
||||
STA.1 ; Nothing follows it yet.
|
||||
INCD.1
|
||||
STA.1
|
||||
INCD.1
|
||||
PSHD.1 ; Where the length goes, once it is known.
|
||||
INCD.1
|
||||
RSTB
|
||||
|
||||
makeNodeLoop:
|
||||
LDA.0
|
||||
BRA makeNodeEnd
|
||||
STA.1
|
||||
INCD.0
|
||||
INCD.1
|
||||
INCB
|
||||
BRI makeNodeLoop
|
||||
|
||||
makeNodeEnd:
|
||||
POPD.0
|
||||
PSHB
|
||||
POPA
|
||||
STA.0 ; How long it turned out to be.
|
||||
INIB 0d3
|
||||
CCF
|
||||
ADD
|
||||
MVQA
|
||||
SETD.0 ArenaFree
|
||||
CALL addByteToWord
|
||||
RET
|
||||
|
||||
; A is a line number. Leaves DP3 on that line and PrevLine on the one before it, which is
|
||||
; zero when it is the first. Q is zero if there is such a line.
|
||||
findLine:
|
||||
SETD.1 Wanted2
|
||||
STA.1
|
||||
INIA 0d1
|
||||
SETD.1 Counted
|
||||
STA.1
|
||||
RSTA
|
||||
SETD.1 PrevLine
|
||||
STA.1
|
||||
INCD.1
|
||||
STA.1
|
||||
SETD.1 TextHead
|
||||
LDD.3.1
|
||||
|
||||
findLineStep:
|
||||
PSHD.3
|
||||
POPA
|
||||
POPB
|
||||
OR
|
||||
BRQ findLineMissing
|
||||
|
||||
SETD.1 Counted
|
||||
LDA.1
|
||||
SETD.1 Wanted2
|
||||
LDB.1
|
||||
XOR
|
||||
BRQ findLineFound
|
||||
|
||||
PSHD.3
|
||||
SETD.1 PrevLine
|
||||
POPD.0
|
||||
STD.0.1
|
||||
PSHD.3
|
||||
POPD.0
|
||||
LDD.3.0 ; On to whatever follows it.
|
||||
SETD.1 Counted
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
BRI findLineStep
|
||||
|
||||
findLineFound:
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
findLineMissing:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; DP3 is a new node and A is the line number it should become. Puts it there.
|
||||
linkBefore:
|
||||
PSHD.3
|
||||
SETD.1 NewLine
|
||||
POPD.0
|
||||
STD.0.1 ; The new node, while the old ones are looked through.
|
||||
|
||||
CALL findLine ; Which may miss, and missing means putting it at the end.
|
||||
|
||||
; What the new node should point at is whatever was there, or nothing.
|
||||
SETD.1 NewLine
|
||||
LDD.0.1
|
||||
BNQ linkBeforeAtEnd
|
||||
PSHD.3
|
||||
POPD.1
|
||||
STD.1.0 ; new.next = the line that was there
|
||||
BRI linkBeforeAttach
|
||||
|
||||
linkBeforeAtEnd:
|
||||
; Nothing was there, so the new one ends the list and goes after whatever was last.
|
||||
RSTA
|
||||
STA.0
|
||||
INCD.0
|
||||
STA.0
|
||||
SETD.1 NewLine
|
||||
LDD.0.1
|
||||
|
||||
linkBeforeAttach:
|
||||
; And whatever came before now points at the new one. Before the first line, that is
|
||||
; the head of the list rather than a node.
|
||||
SETD.1 PrevLine
|
||||
LDD.2.1
|
||||
PSHD.2
|
||||
POPA
|
||||
POPB
|
||||
OR
|
||||
BRQ linkBeforeHead
|
||||
|
||||
SETD.1 NewLine
|
||||
LDD.0.1
|
||||
SETD.1 PrevLine
|
||||
LDD.1.1
|
||||
STD.0.1
|
||||
RET
|
||||
|
||||
linkBeforeHead:
|
||||
SETD.1 NewLine
|
||||
LDD.0.1
|
||||
SETD.1 TextHead
|
||||
STD.0.1
|
||||
RET
|
||||
|
||||
; A is a line number. Takes it out of the list. Q is zero if there was such a line.
|
||||
unlinkLine:
|
||||
CALL findLine
|
||||
BNQ unlinkMissing
|
||||
|
||||
; What follows the one being taken out.
|
||||
PSHD.3
|
||||
POPD.0
|
||||
LDD.0.0
|
||||
|
||||
SETD.1 PrevLine
|
||||
LDD.2.1
|
||||
PSHD.2
|
||||
POPA
|
||||
POPB
|
||||
OR
|
||||
BRQ unlinkHead
|
||||
|
||||
SETD.1 PrevLine
|
||||
LDD.1.1
|
||||
STD.0.1
|
||||
BRI unlinkDone
|
||||
|
||||
unlinkHead:
|
||||
SETD.1 TextHead
|
||||
STD.0.1
|
||||
|
||||
unlinkDone:
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
unlinkMissing:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; Q is how many lines there are.
|
||||
countLines:
|
||||
RSTA
|
||||
SETD.1 Counted
|
||||
STA.1
|
||||
SETD.1 TextHead
|
||||
LDD.3.1
|
||||
countStep:
|
||||
PSHD.3
|
||||
POPA
|
||||
POPB
|
||||
OR
|
||||
BRQ countDone
|
||||
SETD.1 Counted
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
PSHD.3
|
||||
POPD.0
|
||||
LDD.3.0
|
||||
BRI countStep
|
||||
countDone:
|
||||
SETD.1 Counted
|
||||
LDA.1
|
||||
RSTB
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
listLines:
|
||||
INIA 0d1
|
||||
SETD.1 Counted
|
||||
STA.1
|
||||
SETD.1 TextHead
|
||||
LDD.3.1
|
||||
listStep:
|
||||
PSHD.3
|
||||
POPA
|
||||
POPB
|
||||
OR
|
||||
BRQ listDone
|
||||
|
||||
SETD.0 Counted
|
||||
LDA.0
|
||||
CALL printByteDecimal
|
||||
SETD.0 ColonText
|
||||
CALL printString
|
||||
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d02
|
||||
LDA.1
|
||||
SETD.1 Leftover
|
||||
STA.1
|
||||
PSHD.3
|
||||
POPD.0
|
||||
DPUP.0 0d03
|
||||
SETD.1 Leftover
|
||||
LDA.1
|
||||
BRA listEmpty
|
||||
listChars:
|
||||
LDA.0
|
||||
OUTA 0x00
|
||||
INCD.0
|
||||
SETD.1 Leftover
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA listChars
|
||||
listEmpty:
|
||||
CALL newLine
|
||||
|
||||
SETD.1 Counted
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
PSHD.3
|
||||
POPD.0
|
||||
LDD.3.0
|
||||
BRI listStep
|
||||
listDone:
|
||||
RET
|
||||
|
||||
; ---- The file ----
|
||||
|
||||
; Reads the file into lines, if there is one. A name that is not on the disk is a new
|
||||
; document rather than a mistake, which is what makes this the way to start one.
|
||||
loadFile:
|
||||
SETD.0 FileName
|
||||
CALL sbfsFind
|
||||
BNQ loadNothing
|
||||
|
||||
SETD.1 0x40 0x00
|
||||
CALL sbfsRead
|
||||
BNQ loadNothing
|
||||
|
||||
; How many bytes came back: the block count is the high byte of the length and the tail
|
||||
; is the low one, which is how a size is put together everywhere on this disk.
|
||||
SETD.0 SbfsFileBlocks
|
||||
DPUP.0 0d01
|
||||
LDA.0
|
||||
SETD.1 ReadLeft
|
||||
STA.1
|
||||
SETD.0 SbfsFileTail
|
||||
LDA.0
|
||||
SETD.1 ReadLeft
|
||||
INCD.1
|
||||
STA.1
|
||||
|
||||
SETD.0 0x40 0x00
|
||||
SETD.1 Entry
|
||||
RSTA
|
||||
SETD.2 EntryLength
|
||||
STA.2
|
||||
|
||||
splitStep:
|
||||
; Anything left?
|
||||
SETD.2 ReadLeft
|
||||
LDA.2
|
||||
INCD.2
|
||||
LDB.2
|
||||
OR
|
||||
BRQ splitLast
|
||||
|
||||
; How long the line is so far is kept in memory rather than in B, because comparing
|
||||
; against a newline needs B and would quietly count the comparison instead of the line.
|
||||
LDA.0
|
||||
INIB 0d10
|
||||
XOR
|
||||
BRQ splitLine
|
||||
|
||||
STA.1 ; A is still the character; an ALU operation does not touch it.
|
||||
INCD.1
|
||||
SETD.2 EntryLength
|
||||
LDA.2
|
||||
INCA
|
||||
STA.2
|
||||
BRI splitOn
|
||||
|
||||
splitLine:
|
||||
RSTA
|
||||
STA.1 ; The line ends here, so it becomes a string.
|
||||
PSHD.0 ; How far through the file we are.
|
||||
SETD.0 Entry
|
||||
CALL makeNode
|
||||
CALL appendNode
|
||||
POPD.0
|
||||
SETD.1 Entry
|
||||
RSTA
|
||||
SETD.2 EntryLength
|
||||
STA.2
|
||||
|
||||
splitOn:
|
||||
INCD.0
|
||||
SETD.2 ReadLeft
|
||||
CALL takeOneOff
|
||||
BRI splitStep
|
||||
|
||||
splitLast:
|
||||
; A file that does not end in a newline still has a last line in it.
|
||||
SETD.2 EntryLength
|
||||
LDA.2
|
||||
BRA loadNothing
|
||||
RSTA
|
||||
STA.1
|
||||
SETD.0 Entry
|
||||
CALL makeNode
|
||||
CALL appendNode
|
||||
loadNothing:
|
||||
RET
|
||||
|
||||
; DP3 is a node. Puts it on the end of the list.
|
||||
appendNode:
|
||||
; The node has to be put somewhere safe first: counting the lines walks the list in DP3,
|
||||
; which is where the node being added is being held.
|
||||
PSHD.3
|
||||
CALL countLines
|
||||
MVQA
|
||||
INCA
|
||||
POPD.3
|
||||
CALL linkBefore
|
||||
RET
|
||||
|
||||
; DP2 is a two byte count. Takes one off it.
|
||||
takeOneOff:
|
||||
DPUP.2 0d01
|
||||
LDA.2
|
||||
DECA
|
||||
STA.2
|
||||
BNC takeOneDone ; No borrow, so the high half is untouched.
|
||||
DPDN.2 0d01
|
||||
LDA.2
|
||||
DECA
|
||||
STA.2
|
||||
RET
|
||||
takeOneDone:
|
||||
RET
|
||||
|
||||
; Builds the whole document at 0x4000 and saves it. Q is zero if it worked.
|
||||
writeFile:
|
||||
SETD.1 0x40 0x00
|
||||
SETD.2 TextHead
|
||||
LDD.3.2
|
||||
|
||||
writeStep:
|
||||
PSHD.3
|
||||
POPA
|
||||
POPB
|
||||
OR
|
||||
BRQ writeOut
|
||||
|
||||
PSHD.3
|
||||
POPD.0
|
||||
DPUP.0 0d02
|
||||
LDA.0
|
||||
SETD.2 Leftover
|
||||
STA.2
|
||||
INCD.0
|
||||
LDA.2
|
||||
BRA writeBreak
|
||||
writeChars:
|
||||
LDA.0
|
||||
STA.1
|
||||
INCD.0
|
||||
INCD.1
|
||||
SETD.2 Leftover
|
||||
LDA.2
|
||||
DECA
|
||||
STA.2
|
||||
BNA writeChars
|
||||
writeBreak:
|
||||
INIA 0d10
|
||||
STA.1
|
||||
INCD.1
|
||||
|
||||
PSHD.3
|
||||
POPD.0
|
||||
LDD.3.0
|
||||
BRI writeStep
|
||||
|
||||
writeOut:
|
||||
; Where the building stopped says how big it is, with no arithmetic worth the name: the
|
||||
; buffer starts on a page boundary at 0x4000, so the high byte less 0x40 is the number of
|
||||
; whole blocks and the low byte is the tail.
|
||||
SETD.2 WroteSize
|
||||
STD.1.2
|
||||
SETD.0 WroteSize
|
||||
LDA.0
|
||||
INIB 0x40
|
||||
CCF
|
||||
SUB
|
||||
MVQA
|
||||
SETD.0 SbfsFileBlocks
|
||||
RSTB
|
||||
STB.0
|
||||
INCD.0
|
||||
STA.0
|
||||
SETD.0 WroteSize
|
||||
INCD.0
|
||||
LDA.0
|
||||
SETD.0 SbfsFileTail
|
||||
STA.0
|
||||
|
||||
; The size the file is about to be, said in bytes, before saving changes what these mean.
|
||||
SETD.0 WroteSize
|
||||
LDA.0
|
||||
INIB 0x40
|
||||
CCF
|
||||
SUB
|
||||
MVQA
|
||||
SETD.0 WroteSize
|
||||
STA.0
|
||||
|
||||
SETD.0 FileName
|
||||
SETD.1 0x40 0x00
|
||||
CALL sbfsSaveFile
|
||||
RET
|
||||
|
||||
; DP0 is a two byte number, A is a byte. Adds the one to the other.
|
||||
addByteToWord:
|
||||
DPUP.0 0d01
|
||||
LDB.0
|
||||
CCF
|
||||
ADD
|
||||
STQ.0
|
||||
DPDN.0 0d01
|
||||
LDA.0
|
||||
RSTB
|
||||
ADD
|
||||
STQ.0
|
||||
RET
|
||||
|
||||
#Data
|
||||
|
||||
#Base 0x1000
|
||||
|
||||
PromptText:
|
||||
"> "
|
||||
EnteringText:
|
||||
": "
|
||||
ColonText:
|
||||
": "
|
||||
CommaText:
|
||||
", "
|
||||
LinesText:
|
||||
" lines"
|
||||
WrittenText:
|
||||
"written, "
|
||||
BytesText:
|
||||
" bytes"
|
||||
DotText:
|
||||
"."
|
||||
WhatText:
|
||||
"l list, a add, i insert, c change, d delete, w write, q quit"
|
||||
NoNameText:
|
||||
"edit what? try: run edit <file>"
|
||||
NoDiskText:
|
||||
"there is no disk"
|
||||
NoLineText:
|
||||
"there is no such line"
|
||||
NeedsLineText:
|
||||
"which line?"
|
||||
NoWriteText:
|
||||
"it would not write"
|
||||
|
||||
FileName:
|
||||
#Reserve 0d24
|
||||
Command:
|
||||
#Reserve 0d41
|
||||
Entry:
|
||||
#Reserve 0d81
|
||||
|
||||
TextHead:
|
||||
0x00 0x00
|
||||
ArenaFree:
|
||||
0x00 0x00
|
||||
PrevLine:
|
||||
0x00 0x00
|
||||
NewLine:
|
||||
0x00 0x00
|
||||
Wanted:
|
||||
0x00
|
||||
Wanted2:
|
||||
0x00
|
||||
Counted:
|
||||
0x00
|
||||
Leftover:
|
||||
0x00
|
||||
EntryLength:
|
||||
0x00
|
||||
ReadLeft:
|
||||
0x00 0x00
|
||||
WroteSize:
|
||||
0x00 0x00
|
||||
|
||||
#Include sbfs.asm
|
||||
#Include text.asm
|
||||
#Include console.asm
|
||||
@@ -0,0 +1,57 @@
|
||||
; A program that is told what to work on.
|
||||
;
|
||||
; Everything loaded before this one did the same thing however it was started, because
|
||||
; there was no way to tell one anything. A tool that edits a document needs to know which
|
||||
; document, and that is the same need a dozen other things will have, so it is a service
|
||||
; rather than something an editor arranges for itself.
|
||||
;
|
||||
; run says it was given nothing
|
||||
; run whatever else says "whatever else"
|
||||
;
|
||||
; The whole rest of the line arrives, spaces and all, rather than a list of words. What
|
||||
; counts as an argument is the program's business; the system's business is handing over
|
||||
; what was typed.
|
||||
|
||||
#Include services.asm
|
||||
|
||||
#Program
|
||||
|
||||
#Base 0x2000
|
||||
|
||||
start:
|
||||
SETD.0 Given
|
||||
INIB 0d64
|
||||
SWI osArgument
|
||||
|
||||
SETD.0 Given
|
||||
LDA.0
|
||||
BRA sayNothing
|
||||
|
||||
SETD.0 SaidText
|
||||
SWI osPrintString
|
||||
SETD.0 Given
|
||||
SWI osPrintString
|
||||
BRI sayEnd
|
||||
|
||||
sayNothing:
|
||||
SETD.0 NothingText
|
||||
SWI osPrintString
|
||||
|
||||
sayEnd:
|
||||
SETD.0 NewLine
|
||||
SWI osPrintString
|
||||
SWI osExit
|
||||
|
||||
#Data
|
||||
|
||||
#Base 0x1000
|
||||
|
||||
SaidText:
|
||||
"it says: "
|
||||
NothingText:
|
||||
"nothing was said"
|
||||
NewLine:
|
||||
0x0A 0x00
|
||||
|
||||
Given:
|
||||
#Reserve 0d64
|
||||
@@ -109,6 +109,16 @@ prompt:
|
||||
CALL textSame
|
||||
BRQ doDump
|
||||
|
||||
SETD.0 CommandLine
|
||||
SETD.1 DeleteName
|
||||
CALL textSame
|
||||
BRQ doDelete
|
||||
|
||||
SETD.0 CommandLine
|
||||
SETD.1 RenameName
|
||||
CALL textSame
|
||||
BRQ doRename
|
||||
|
||||
SETD.0 CommandLine
|
||||
SETD.1 HelpName
|
||||
CALL textSame
|
||||
@@ -195,7 +205,16 @@ dirDone:
|
||||
SETD.0 DirSeen
|
||||
LDA.0
|
||||
CALL printByteDecimal
|
||||
; One file is not one files. Cheap to get right and it reads as carelessness otherwise.
|
||||
SETD.0 DirSeen
|
||||
LDA.0
|
||||
DECA
|
||||
BRA dirOne
|
||||
SETD.0 FilesText
|
||||
BRI dirCount
|
||||
dirOne:
|
||||
SETD.0 FileText
|
||||
dirCount:
|
||||
CALL printString
|
||||
CALL newLine
|
||||
BRI prompt
|
||||
@@ -470,6 +489,84 @@ loadComplain:
|
||||
CALL newLine
|
||||
BRI prompt
|
||||
|
||||
; ---- delete and rename ----
|
||||
;
|
||||
; The two things a disk needs that reading and writing do not provide, and the two that
|
||||
; anything editing a document will want from the shell as well as from a program. Deleting
|
||||
; frees an entry and its blocks; renaming changes twenty two bytes and moves nothing.
|
||||
|
||||
doDelete:
|
||||
SETD.0 DiskReady
|
||||
LDA.0
|
||||
BRA fileNoDisk
|
||||
|
||||
SETD.1 TextRest
|
||||
LDD.0.1
|
||||
LDA.0
|
||||
BRA deleteWhat
|
||||
|
||||
CALL sbfsDelete
|
||||
BNQ deleteFailed
|
||||
SETD.0 Deleted
|
||||
CALL printString
|
||||
CALL newLine
|
||||
BRI prompt
|
||||
|
||||
deleteWhat:
|
||||
SETD.0 DeleteWhat
|
||||
BRI fileComplain
|
||||
deleteFailed:
|
||||
SETD.0 NoSuchFile
|
||||
BRI fileComplain
|
||||
|
||||
doRename:
|
||||
SETD.0 DiskReady
|
||||
LDA.0
|
||||
BRA fileNoDisk
|
||||
|
||||
SETD.1 TextRest
|
||||
LDD.0.1
|
||||
LDA.0
|
||||
BRA renameWhat
|
||||
|
||||
; Two names, so the rest of the line is split again. textSplit writes a zero over the
|
||||
; space it cuts at, so what was one string becomes two without anything being copied.
|
||||
SETD.1 TextRest
|
||||
LDD.0.1
|
||||
SETD.1 RenameFrom
|
||||
STD.0.1
|
||||
CALL textSplit
|
||||
|
||||
SETD.1 TextRest
|
||||
LDD.1.1
|
||||
LDA.1
|
||||
BRA renameWhat ; Only one name was given, and this needs both.
|
||||
|
||||
SETD.2 RenameFrom
|
||||
LDD.0.2
|
||||
CALL sbfsRename
|
||||
BNQ renameFailed
|
||||
SETD.0 Renamed
|
||||
CALL printString
|
||||
CALL newLine
|
||||
BRI prompt
|
||||
|
||||
renameWhat:
|
||||
SETD.0 RenameWhat
|
||||
BRI fileComplain
|
||||
renameFailed:
|
||||
; Either there is no such file or the new name is already taken. Which of the two is not
|
||||
; worth another message: both mean the disk does not have room for that name to move.
|
||||
SETD.0 RenameNo
|
||||
BRI fileComplain
|
||||
|
||||
fileNoDisk:
|
||||
SETD.0 NoDisk
|
||||
fileComplain:
|
||||
CALL printString
|
||||
CALL newLine
|
||||
BRI prompt
|
||||
|
||||
; ---- run ----
|
||||
;
|
||||
; Hands the machine to whatever was loaded. Where the Stack is now is written down first,
|
||||
@@ -485,6 +582,15 @@ doRun:
|
||||
SETD.1 SystemStack
|
||||
STD.0.1
|
||||
|
||||
; Whatever followed the word "run" is kept where the program can ask for it. Copied
|
||||
; rather than pointed at, because what it is pointing at is the line the shell typed
|
||||
; into, and a program is entitled to outlive the shell's opinion of that.
|
||||
SETD.1 TextRest
|
||||
LDD.0.1
|
||||
SETD.1 RunArgument
|
||||
INIB 0d64
|
||||
CALL copyText
|
||||
|
||||
CALL installVectors
|
||||
|
||||
; The entry address is a number until BRD makes it a place. DP3 is the one to build it
|
||||
@@ -499,6 +605,26 @@ runNothing:
|
||||
CALL newLine
|
||||
BRI prompt
|
||||
|
||||
; DP0 is a string, DP1 is where it should go, and B is how much room there is counting
|
||||
; the zero on the end. What does not fit is left behind, and what is written is a string
|
||||
; either way.
|
||||
copyText:
|
||||
BRB copyTextDone ; No room at all, so nothing is written, not even the zero.
|
||||
copyTextLoop:
|
||||
DECB
|
||||
BRB copyTextEnd ; Only room for the terminator now.
|
||||
LDA.0
|
||||
STA.1
|
||||
BRA copyTextDone
|
||||
INCD.0
|
||||
INCD.1
|
||||
BRI copyTextLoop
|
||||
copyTextEnd:
|
||||
RSTA
|
||||
STA.1
|
||||
copyTextDone:
|
||||
RET
|
||||
|
||||
; ---- Putting a program's vectors in, and taking them out again ----
|
||||
;
|
||||
; The vector table lives in Program Memory, which no instruction can write, so both of
|
||||
@@ -635,6 +761,20 @@ handleReadLine:
|
||||
CALL readLine
|
||||
RETI
|
||||
|
||||
; What the program was asked to work on. DP0 says where to put it and B how much room
|
||||
; there is, counting the zero on the end, which is the same bargain readLine offers.
|
||||
;
|
||||
; Being asked for rather than left at an agreed address is deliberate. The two sides of
|
||||
; this already have to agree on a vector number and nothing else, and that number is
|
||||
; written down once in services.asm; an address would be a second thing to agree about, in
|
||||
; a memory map that is a convention rather than anything enforced.
|
||||
handleArgument:
|
||||
PSHD.0
|
||||
POPD.1
|
||||
SETD.0 RunArgument
|
||||
CALL copyText
|
||||
RETI
|
||||
|
||||
; Giving the machine back. This is the one place MVDS earns its keep. The program's Stack,
|
||||
; and the frame this very interrupt arrived on, are both abandoned where they lie, because
|
||||
; nothing is going to return through either of them.
|
||||
@@ -893,13 +1033,17 @@ Farewell:
|
||||
"halted"
|
||||
FilesText:
|
||||
" files"
|
||||
FileText:
|
||||
" file"
|
||||
|
||||
; Two strings rather than one, because a string literal stops at 255 characters and each
|
||||
; one carries its own zero byte, so they are printed in turn rather than joined.
|
||||
HelpText:
|
||||
"dir list what is on the disk
|
||||
load <file> read a program off the disk
|
||||
run start what was loaded"
|
||||
run [words] start what was loaded, and tell it those words
|
||||
delete <file> take it off the disk
|
||||
rename <file> <to> call it something else"
|
||||
HelpMoreText:
|
||||
"dump sixty four bytes of memory, and again for more
|
||||
dump <program|data|bank> <address>
|
||||
@@ -923,6 +1067,16 @@ LoadWhat:
|
||||
"load what?"
|
||||
NoSuchFile:
|
||||
"no such file"
|
||||
Deleted:
|
||||
"gone"
|
||||
Renamed:
|
||||
"renamed"
|
||||
DeleteWhat:
|
||||
"delete what?"
|
||||
RenameWhat:
|
||||
"rename what to what?"
|
||||
RenameNo:
|
||||
"there is no such file, or that name is taken"
|
||||
TooManyVectors:
|
||||
"that program wants more vectors than there is room for"
|
||||
Unreadable:
|
||||
@@ -944,6 +1098,10 @@ LoadName:
|
||||
"load"
|
||||
RunName:
|
||||
"run"
|
||||
DeleteName:
|
||||
"delete"
|
||||
RenameName:
|
||||
"rename"
|
||||
HelpName:
|
||||
"help"
|
||||
ExitName:
|
||||
@@ -960,6 +1118,15 @@ LoadCount:
|
||||
LoadVersion:
|
||||
0x00
|
||||
|
||||
; Where the first of rename's two names is, kept while the second is picked out of the
|
||||
; line, since finding that needs the pointers for itself.
|
||||
RenameFrom:
|
||||
0x00 0x00
|
||||
|
||||
; What followed "run", kept for the program to ask for.
|
||||
RunArgument:
|
||||
#Reserve 0d64
|
||||
|
||||
; ---- The vectors a loaded program brought with it ----
|
||||
;
|
||||
; Six bytes each: where it goes, what goes there, and what was there before. The last two
|
||||
@@ -1013,4 +1180,5 @@ CommandLine:
|
||||
osPrintString handlePrintString
|
||||
osReadLine handleReadLine
|
||||
osExit handleExit
|
||||
osArgument handleArgument
|
||||
Device 0x20 diskDone
|
||||
|
||||
@@ -991,6 +991,208 @@ sbfsSameByte:
|
||||
XOR
|
||||
RET
|
||||
|
||||
; ---- Deleting ----
|
||||
;
|
||||
; Frees a file. DP0 names it, and Q is zero if it went.
|
||||
;
|
||||
; A deleted entry and one that was never used are the same thing, which is the whole of
|
||||
; what deleting is here: the entry is zeroed and its blocks stop being spoken for. THE
|
||||
; BLOCKS THEMSELVES ARE LEFT EXACTLY AS THEY WERE, so what was in a file is still on the
|
||||
; disk until something is put over the top of it. Worth knowing if anything is ever meant
|
||||
; to be private. The host tool does the same, so the two agree about what a deleted disk
|
||||
; looks like.
|
||||
;
|
||||
; Nothing is compacted. Deleting leaves a hole, and because files are contiguous a hole is
|
||||
; only usable by something that fits inside it. That is the price of the directory being
|
||||
; the whole allocation map, and tidying it up is an ordinary program somebody can write
|
||||
; rather than anything the format has to say.
|
||||
sbfsDelete:
|
||||
CALL sbfsFind
|
||||
BNQ sbfsDeleteFailed
|
||||
|
||||
; How much room it was taking, worked out before the entry that says so is thrown away.
|
||||
CALL sbfsFileExtent
|
||||
|
||||
; sbfsFind leaves DP3 on the entry and SbfsBlock on the directory block it came out of,
|
||||
; which is everything needed to change it and put it back.
|
||||
PSHD.3
|
||||
POPD.0
|
||||
INIB 0d32
|
||||
sbfsDeleteWipe:
|
||||
RSTA
|
||||
STA.0
|
||||
INCD.0
|
||||
DECB
|
||||
BNB sbfsDeleteWipe
|
||||
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferIn
|
||||
CALL sbfsWriteBlock
|
||||
BNQ sbfsDeleteFailed
|
||||
|
||||
; And the free count goes back up. It is a note rather than the truth, but a note worth
|
||||
; keeping right.
|
||||
RSTA
|
||||
SETD.0 SbfsBlock
|
||||
STA.0
|
||||
INCD.0
|
||||
STA.0
|
||||
CALL sbfsReadBlock
|
||||
BNQ sbfsDeleteFailed
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferOut
|
||||
SETD.0 SbfsBuffer
|
||||
DPUP.0 0d12
|
||||
SETD.2 SbfsWantBlocks
|
||||
CALL sbfsAddWord
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferIn
|
||||
CALL sbfsWriteBlock
|
||||
RET
|
||||
|
||||
sbfsDeleteFailed:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; ---- Renaming ----
|
||||
;
|
||||
; DP0 is the name a file has, DP1 is the name it should have. Q is zero if it was renamed.
|
||||
;
|
||||
; Only the twenty two bytes of the name change, so no data moves and no block is touched
|
||||
; but the one holding the entry. THAT IS WHAT MAKES A SAFE SAVE POSSIBLE. Writing a file
|
||||
; that has grown means putting it somewhere else, and the obvious order - throw the old one
|
||||
; away, then write the new one - loses the lot if there turns out to be nowhere to put it.
|
||||
; Renaming is the cheapest thing this filesystem can do and the only one that can be left
|
||||
; until last, so it is what the order is built around.
|
||||
sbfsRename:
|
||||
; Where the old name is, kept somewhere that finding things will not tread on.
|
||||
SETD.2 SbfsSavedName
|
||||
STD.0.2
|
||||
|
||||
PSHD.1
|
||||
POPD.0
|
||||
SETD.1 SbfsNewName
|
||||
CALL sbfsKeepName ; Twenty two bytes, padded, the way an entry holds one.
|
||||
|
||||
; Refused if something already answers to the new name. Two entries with one name is a
|
||||
; disk that cannot be searched sensibly: a search answers with whichever it meets first,
|
||||
; so the other becomes unreachable without ever having been deleted.
|
||||
SETD.0 SbfsNewName
|
||||
CALL sbfsFind
|
||||
BRQ sbfsRenameFailed
|
||||
|
||||
SETD.2 SbfsSavedName
|
||||
LDD.0.2
|
||||
CALL sbfsFind
|
||||
BNQ sbfsRenameFailed
|
||||
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d06 ; Past the flags, the start, the block count and the tail.
|
||||
SETD.0 SbfsNewName
|
||||
INIA 0d22
|
||||
SETD.2 SbfsCount
|
||||
STA.2
|
||||
sbfsRenameName:
|
||||
LDA.0
|
||||
STA.1
|
||||
INCD.0
|
||||
INCD.1
|
||||
LDA.2
|
||||
DECA
|
||||
STA.2
|
||||
BNA sbfsRenameName
|
||||
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferIn
|
||||
CALL sbfsWriteBlock
|
||||
RET
|
||||
|
||||
sbfsRenameFailed:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; ---- Saving over something that is already there ----
|
||||
;
|
||||
; DP0 names the file, DP1 is the data, and SbfsFileBlocks with SbfsFileTail say how big it
|
||||
; now is. Q is zero if it was saved.
|
||||
;
|
||||
; This is the routine every tool that edits a document wants, and the reason it is here
|
||||
; rather than in each of them is that the careful order is not obvious and getting it wrong
|
||||
; destroys somebody's work:
|
||||
;
|
||||
; make a temporary nothing is lost if there is nowhere to put it
|
||||
; write it
|
||||
; delete the original only once the new one is safely down
|
||||
; rename the temporary
|
||||
;
|
||||
; The obvious order - delete, create, write - looks fine and is a trap. Files here are
|
||||
; contiguous, so a file that has grown may not fit where it was, and a create can be
|
||||
; refused for want of a run long enough even on a disk with plenty of free blocks. Do it
|
||||
; that way round and the original is already gone when that happens.
|
||||
sbfsSaveFile:
|
||||
SETD.2 SbfsSaveName
|
||||
STD.0.2
|
||||
SETD.2 SbfsSaveData
|
||||
STD.1.2
|
||||
|
||||
; How big it is, kept aside: finding and deleting things both overwrite the place the
|
||||
; size is normally said, because both of them describe whatever they last looked at.
|
||||
SETD.0 SbfsSaveBlocks
|
||||
SETD.2 SbfsFileBlocks
|
||||
CALL sbfsSetWord
|
||||
SETD.0 SbfsFileTail
|
||||
LDA.0
|
||||
SETD.1 SbfsSaveTail
|
||||
STA.1
|
||||
|
||||
; A temporary left behind by a save that did not finish would be in the way. Whether
|
||||
; there was one is not worth asking about, since either answer leads here.
|
||||
SETD.0 SbfsTempName
|
||||
CALL sbfsDelete
|
||||
|
||||
SETD.0 SbfsFileBlocks
|
||||
SETD.2 SbfsSaveBlocks
|
||||
CALL sbfsSetWord
|
||||
SETD.0 SbfsSaveTail
|
||||
LDA.0
|
||||
SETD.1 SbfsFileTail
|
||||
STA.1
|
||||
|
||||
SETD.0 SbfsTempName
|
||||
CALL sbfsCreate
|
||||
BNQ sbfsSaveFailed
|
||||
|
||||
SETD.2 SbfsSaveData
|
||||
LDD.1.2
|
||||
CALL sbfsWriteFile
|
||||
BNQ sbfsSaveFailed
|
||||
|
||||
; Now, and not before, the old one goes. It may not exist, which is what saving something
|
||||
; for the first time looks like from here.
|
||||
SETD.2 SbfsSaveName
|
||||
LDD.0.2
|
||||
CALL sbfsDelete
|
||||
|
||||
SETD.0 SbfsTempName
|
||||
SETD.2 SbfsSaveName
|
||||
LDD.1.2
|
||||
CALL sbfsRename
|
||||
RET
|
||||
|
||||
sbfsSaveFailed:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
#Data
|
||||
|
||||
SbfsMagic:
|
||||
@@ -1050,5 +1252,29 @@ SbfsName:
|
||||
SbfsWanted:
|
||||
#Reserve 0d22
|
||||
|
||||
; ---- What renaming and saving keep ----
|
||||
|
||||
; A name on its way into an entry, padded to the twenty two bytes an entry holds.
|
||||
SbfsNewName:
|
||||
#Reserve 0d22
|
||||
|
||||
; Where a name lives, kept across a search, which needs the pointers for itself.
|
||||
SbfsSavedName:
|
||||
0x00 0x00
|
||||
|
||||
SbfsSaveName:
|
||||
0x00 0x00
|
||||
SbfsSaveData:
|
||||
0x00 0x00
|
||||
SbfsSaveBlocks:
|
||||
0x00 0x00
|
||||
SbfsSaveTail:
|
||||
0x00
|
||||
|
||||
; What a document is called while it is being written and is not yet the real thing. A
|
||||
; name nothing else is likely to want, and short enough to leave room for a long one.
|
||||
SbfsTempName:
|
||||
"sbfs.part"
|
||||
|
||||
SbfsBuffer:
|
||||
#Reserve 0d256
|
||||
|
||||
@@ -24,3 +24,4 @@
|
||||
osPrintString 0d16 ; DP0 names a string. Prints it.
|
||||
osReadLine 0d17 ; DP0 names somewhere to put a line read from the console.
|
||||
osExit 0d18 ; Give the machine back to the system.
|
||||
osArgument 0d19 ; DP0 names somewhere to put the rest of the run command.
|
||||
|
||||
@@ -215,6 +215,82 @@ textHexNo:
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user