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
|
CALL textSame
|
||||||
BRQ doDump
|
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.0 CommandLine
|
||||||
SETD.1 HelpName
|
SETD.1 HelpName
|
||||||
CALL textSame
|
CALL textSame
|
||||||
@@ -195,7 +205,16 @@ dirDone:
|
|||||||
SETD.0 DirSeen
|
SETD.0 DirSeen
|
||||||
LDA.0
|
LDA.0
|
||||||
CALL printByteDecimal
|
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
|
SETD.0 FilesText
|
||||||
|
BRI dirCount
|
||||||
|
dirOne:
|
||||||
|
SETD.0 FileText
|
||||||
|
dirCount:
|
||||||
CALL printString
|
CALL printString
|
||||||
CALL newLine
|
CALL newLine
|
||||||
BRI prompt
|
BRI prompt
|
||||||
@@ -470,6 +489,84 @@ loadComplain:
|
|||||||
CALL newLine
|
CALL newLine
|
||||||
BRI prompt
|
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 ----
|
; ---- run ----
|
||||||
;
|
;
|
||||||
; Hands the machine to whatever was loaded. Where the Stack is now is written down first,
|
; Hands the machine to whatever was loaded. Where the Stack is now is written down first,
|
||||||
@@ -485,6 +582,15 @@ doRun:
|
|||||||
SETD.1 SystemStack
|
SETD.1 SystemStack
|
||||||
STD.0.1
|
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
|
CALL installVectors
|
||||||
|
|
||||||
; The entry address is a number until BRD makes it a place. DP3 is the one to build it
|
; 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
|
CALL newLine
|
||||||
BRI prompt
|
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 ----
|
; ---- 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
|
; The vector table lives in Program Memory, which no instruction can write, so both of
|
||||||
@@ -635,6 +761,20 @@ handleReadLine:
|
|||||||
CALL readLine
|
CALL readLine
|
||||||
RETI
|
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,
|
; 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
|
; and the frame this very interrupt arrived on, are both abandoned where they lie, because
|
||||||
; nothing is going to return through either of them.
|
; nothing is going to return through either of them.
|
||||||
@@ -893,13 +1033,17 @@ Farewell:
|
|||||||
"halted"
|
"halted"
|
||||||
FilesText:
|
FilesText:
|
||||||
" files"
|
" files"
|
||||||
|
FileText:
|
||||||
|
" file"
|
||||||
|
|
||||||
; Two strings rather than one, because a string literal stops at 255 characters and each
|
; 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.
|
; one carries its own zero byte, so they are printed in turn rather than joined.
|
||||||
HelpText:
|
HelpText:
|
||||||
"dir list what is on the disk
|
"dir list what is on the disk
|
||||||
load <file> read a program off 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:
|
HelpMoreText:
|
||||||
"dump sixty four bytes of memory, and again for more
|
"dump sixty four bytes of memory, and again for more
|
||||||
dump <program|data|bank> <address>
|
dump <program|data|bank> <address>
|
||||||
@@ -923,6 +1067,16 @@ LoadWhat:
|
|||||||
"load what?"
|
"load what?"
|
||||||
NoSuchFile:
|
NoSuchFile:
|
||||||
"no such file"
|
"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:
|
TooManyVectors:
|
||||||
"that program wants more vectors than there is room for"
|
"that program wants more vectors than there is room for"
|
||||||
Unreadable:
|
Unreadable:
|
||||||
@@ -944,6 +1098,10 @@ LoadName:
|
|||||||
"load"
|
"load"
|
||||||
RunName:
|
RunName:
|
||||||
"run"
|
"run"
|
||||||
|
DeleteName:
|
||||||
|
"delete"
|
||||||
|
RenameName:
|
||||||
|
"rename"
|
||||||
HelpName:
|
HelpName:
|
||||||
"help"
|
"help"
|
||||||
ExitName:
|
ExitName:
|
||||||
@@ -960,6 +1118,15 @@ LoadCount:
|
|||||||
LoadVersion:
|
LoadVersion:
|
||||||
0x00
|
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 ----
|
; ---- 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
|
; Six bytes each: where it goes, what goes there, and what was there before. The last two
|
||||||
@@ -1013,4 +1180,5 @@ CommandLine:
|
|||||||
osPrintString handlePrintString
|
osPrintString handlePrintString
|
||||||
osReadLine handleReadLine
|
osReadLine handleReadLine
|
||||||
osExit handleExit
|
osExit handleExit
|
||||||
|
osArgument handleArgument
|
||||||
Device 0x20 diskDone
|
Device 0x20 diskDone
|
||||||
|
|||||||
@@ -991,6 +991,208 @@ sbfsSameByte:
|
|||||||
XOR
|
XOR
|
||||||
RET
|
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
|
#Data
|
||||||
|
|
||||||
SbfsMagic:
|
SbfsMagic:
|
||||||
@@ -1050,5 +1252,29 @@ SbfsName:
|
|||||||
SbfsWanted:
|
SbfsWanted:
|
||||||
#Reserve 0d22
|
#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:
|
SbfsBuffer:
|
||||||
#Reserve 0d256
|
#Reserve 0d256
|
||||||
|
|||||||
@@ -24,3 +24,4 @@
|
|||||||
osPrintString 0d16 ; DP0 names a string. Prints it.
|
osPrintString 0d16 ; DP0 names a string. Prints it.
|
||||||
osReadLine 0d17 ; DP0 names somewhere to put a line read from the console.
|
osReadLine 0d17 ; DP0 names somewhere to put a line read from the console.
|
||||||
osExit 0d18 ; Give the machine back to the system.
|
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
|
ADD
|
||||||
RET
|
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
|
#Data
|
||||||
|
|
||||||
; Where the rest of the line begins, after textSplit has taken a word off the front.
|
; Where the rest of the line begins, after textSplit has taken a word off the front.
|
||||||
|
|||||||
+1
-1
@@ -76,7 +76,7 @@ cosmos: $(COSMOS) $(APPS)
|
|||||||
$(COSMOS_DISK): $(APPS)
|
$(COSMOS_DISK): $(APPS)
|
||||||
@mkdir -p $(@D)
|
@mkdir -p $(@D)
|
||||||
rm -f $@
|
rm -f $@
|
||||||
$(DISKTOOL) format $@ 64 2
|
$(DISKTOOL) format $@ 256 2
|
||||||
@for app in $(APPS); do $(DISKTOOL) put $@ $$app; done
|
@for app in $(APPS); do $(DISKTOOL) put $@ $$app; done
|
||||||
|
|
||||||
cosmos-disk: $(COSMOS_DISK)
|
cosmos-disk: $(COSMOS_DISK)
|
||||||
|
|||||||
@@ -0,0 +1,233 @@
|
|||||||
|
; Deleting, renaming, and saving over something that is already there.
|
||||||
|
;
|
||||||
|
; Reading and writing were built first because a program had to be got onto a disk and off
|
||||||
|
; it again. What a document needs is different and it is all about the second time: a file
|
||||||
|
; that is written once is easy, and a file that is written again having grown is where this
|
||||||
|
; format's bargain shows. Files are contiguous and do not grow, so saving a longer version
|
||||||
|
; means putting it somewhere else and letting go of where it was.
|
||||||
|
;
|
||||||
|
; THE ORDER MATTERS AND THE OBVIOUS ONE IS WRONG. Delete the old, create the new, write it:
|
||||||
|
; that loses the lot when the create is refused for want of a run long enough, which is a
|
||||||
|
; thing that happens on a disk with plenty of free blocks once it is in pieces. sbfsSaveFile
|
||||||
|
; does it the other way round, and the rename at the end of that is why renaming exists.
|
||||||
|
;
|
||||||
|
; Correct output is:
|
||||||
|
; here.txt 0002 already here
|
||||||
|
; doc.txt 0003 first draft
|
||||||
|
; doc.txt 0004 a second draft, which is longer than the first
|
||||||
|
; notes.txt 0004 a second draft, which is longer than the first
|
||||||
|
; doc.txt gone
|
||||||
|
; notes.txt gone
|
||||||
|
;
|
||||||
|
; The start block moving from 0003 to 0004 is the file being put somewhere else, which is
|
||||||
|
; what saving a longer one has to do. The last two lines are a rename and a delete having
|
||||||
|
; actually happened rather than having been reported.
|
||||||
|
|
||||||
|
#Include print.asm
|
||||||
|
#Include sbfs.asm
|
||||||
|
|
||||||
|
#Program
|
||||||
|
|
||||||
|
start:
|
||||||
|
CALL sbfsMount
|
||||||
|
BNQ failed
|
||||||
|
|
||||||
|
SETD.0 Existing
|
||||||
|
CALL report
|
||||||
|
BNQ failed
|
||||||
|
|
||||||
|
; Written once, the ordinary way.
|
||||||
|
SETD.0 DocName
|
||||||
|
SETD.1 ShortText
|
||||||
|
INIA 0d11
|
||||||
|
CALL makeAndWrite
|
||||||
|
BNQ failed
|
||||||
|
SETD.0 DocName
|
||||||
|
CALL report
|
||||||
|
BNQ failed
|
||||||
|
|
||||||
|
; And again, longer. Nothing here says where it goes; that is sbfsSaveFile's business.
|
||||||
|
SETD.0 SbfsFileBlocks
|
||||||
|
RSTA
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
STA.0
|
||||||
|
SETD.0 SbfsFileTail
|
||||||
|
INIA 0d46
|
||||||
|
STA.0
|
||||||
|
SETD.0 DocName
|
||||||
|
SETD.1 LongText
|
||||||
|
CALL sbfsSaveFile
|
||||||
|
BNQ failed
|
||||||
|
SETD.0 DocName
|
||||||
|
CALL report
|
||||||
|
BNQ failed
|
||||||
|
|
||||||
|
; Renaming moves nothing: the same blocks answer to a different name.
|
||||||
|
SETD.0 DocName
|
||||||
|
SETD.1 NewName
|
||||||
|
CALL sbfsRename
|
||||||
|
BNQ failed
|
||||||
|
SETD.0 NewName
|
||||||
|
CALL report
|
||||||
|
BNQ failed
|
||||||
|
|
||||||
|
; And the old name is not there any more, which is the half of renaming that could have
|
||||||
|
; quietly not happened.
|
||||||
|
SETD.0 DocName
|
||||||
|
CALL expectGone
|
||||||
|
|
||||||
|
SETD.0 NewName
|
||||||
|
CALL sbfsDelete
|
||||||
|
BNQ failed
|
||||||
|
SETD.0 NewName
|
||||||
|
CALL expectGone
|
||||||
|
HALT
|
||||||
|
|
||||||
|
failed:
|
||||||
|
SETD.0 Failed
|
||||||
|
CALL printString
|
||||||
|
CALL lineFeed
|
||||||
|
HALT
|
||||||
|
|
||||||
|
; DP0 names the file, DP1 is its text, A is how long it is. Everything here fits in a
|
||||||
|
; block, so the whole length is the tail.
|
||||||
|
makeAndWrite:
|
||||||
|
PSHD.1
|
||||||
|
SETD.1 SbfsFileTail
|
||||||
|
STA.1
|
||||||
|
RSTA
|
||||||
|
SETD.1 SbfsFileBlocks
|
||||||
|
STA.1
|
||||||
|
INCD.1
|
||||||
|
STA.1
|
||||||
|
CALL sbfsCreate
|
||||||
|
POPD.1
|
||||||
|
BNQ makeFailed
|
||||||
|
CALL sbfsWriteFile
|
||||||
|
RET
|
||||||
|
makeFailed:
|
||||||
|
RET
|
||||||
|
|
||||||
|
; DP0 names a file that should not be there. Says so either way.
|
||||||
|
expectGone:
|
||||||
|
PSHD.0
|
||||||
|
POPD.3
|
||||||
|
CALL printString
|
||||||
|
CALL padName
|
||||||
|
PSHD.3
|
||||||
|
POPD.0
|
||||||
|
CALL sbfsFind
|
||||||
|
BRQ goneStillThere
|
||||||
|
SETD.0 GoneText
|
||||||
|
CALL printString
|
||||||
|
CALL lineFeed
|
||||||
|
RET
|
||||||
|
goneStillThere:
|
||||||
|
SETD.0 StillText
|
||||||
|
CALL printString
|
||||||
|
CALL lineFeed
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Prints a file's name, where it begins, and what is in it.
|
||||||
|
report:
|
||||||
|
PSHD.0
|
||||||
|
POPD.3
|
||||||
|
CALL printString
|
||||||
|
CALL padName
|
||||||
|
PSHD.3
|
||||||
|
POPD.0
|
||||||
|
CALL sbfsFind
|
||||||
|
BNQ reportFailed
|
||||||
|
|
||||||
|
SETD.0 SbfsFileStart
|
||||||
|
LDA.0
|
||||||
|
CALL printByteHex
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
CALL printByteHex
|
||||||
|
CALL blankSpace
|
||||||
|
|
||||||
|
SETD.1 Landing
|
||||||
|
CALL sbfsRead
|
||||||
|
BNQ reportFailed
|
||||||
|
|
||||||
|
SETD.0 Landing
|
||||||
|
SETD.1 SbfsFileTail
|
||||||
|
LDA.1
|
||||||
|
SETD.1 LeftOver
|
||||||
|
STA.1
|
||||||
|
BRA reportEnd
|
||||||
|
reportLoop:
|
||||||
|
LDA.0
|
||||||
|
OUTA 0x00
|
||||||
|
INCD.0
|
||||||
|
SETD.1 LeftOver
|
||||||
|
LDA.1
|
||||||
|
DECA
|
||||||
|
STA.1
|
||||||
|
BNA reportLoop
|
||||||
|
reportEnd:
|
||||||
|
CALL lineFeed
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
reportFailed:
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Names are different lengths and the columns should not be, so this pads out to eleven.
|
||||||
|
; DP3 holds the name, which is where the caller left it.
|
||||||
|
padName:
|
||||||
|
PSHD.3
|
||||||
|
POPD.0
|
||||||
|
INIB 0d11
|
||||||
|
padCount:
|
||||||
|
LDA.0
|
||||||
|
BRA padOut
|
||||||
|
INCD.0
|
||||||
|
DECB
|
||||||
|
BNB padCount
|
||||||
|
padOut:
|
||||||
|
RSTA
|
||||||
|
padLoop:
|
||||||
|
BRB padDone
|
||||||
|
INIA 0x20
|
||||||
|
OUTA 0x00
|
||||||
|
DECB
|
||||||
|
BRI padLoop
|
||||||
|
padDone:
|
||||||
|
RET
|
||||||
|
|
||||||
|
#Data
|
||||||
|
|
||||||
|
Existing:
|
||||||
|
"here.txt"
|
||||||
|
DocName:
|
||||||
|
"doc.txt"
|
||||||
|
NewName:
|
||||||
|
"notes.txt"
|
||||||
|
|
||||||
|
ShortText:
|
||||||
|
"first draft"
|
||||||
|
LongText:
|
||||||
|
"a second draft, which is longer than the first"
|
||||||
|
|
||||||
|
GoneText:
|
||||||
|
"gone"
|
||||||
|
StillText:
|
||||||
|
"STILL THERE"
|
||||||
|
Failed:
|
||||||
|
"failed"
|
||||||
|
|
||||||
|
LeftOver:
|
||||||
|
0x00
|
||||||
|
|
||||||
|
Landing:
|
||||||
|
#Reserve 0d256
|
||||||
@@ -110,6 +110,10 @@ Disk images that tests read from are built by Tests/makedisks.sh before the run,
|
|||||||
|
|
||||||
The disk tool is checked separately by Tests/disk.sh, which make test runs afterwards: it puts files of every awkward size onto an image and takes them off again, and checks that the things the format says cannot happen are refused.
|
The disk tool is checked separately by Tests/disk.sh, which make test runs afterwards: it puts files of every awkward size onto an image and takes them off again, and checks that the things the format says cannot happen are refused.
|
||||||
|
|
||||||
|
Tests/terminal.sh checks the things a recorded output cannot see. Every other test pipes input in and output to a file, which answers what a program prints and is blind to two whole classes of behaviour: **when** something is printed, since piped output is buffered and flushed at exit, so a prompt shown before its answer is asked for and one shown an hour late produce identical files; and **what happens to the terminal**, since key mode only touches one when there is one. Both have gone wrong here, and both were found by a person whose terminal stopped working rather than by anything in this suite. So it runs the emulator under a pseudo-terminal and asks the questions directly: that a prompt arrives before input is read, that a keystroke arrives without Return, that the terminal is handed back however the machine dies, and that suspending and resuming leave it as they found it.
|
||||||
|
|
||||||
|
A cycle count is deliberately **not** part of a recorded result. The last line of the emulator's output has the number taken out of it before anything is compared, keeping only whether the program stopped on its own or ran into its limit, which is behaviour. Two instructions added to CosmOS used to move that number in six unrelated files at once, so a real difference would arrive in a crowd of meaningless ones. Anything that wants to measure cycles should say so in a test of its own.
|
||||||
|
|
||||||
Tests/docs.sh then checks the manuals against the code: that every instruction has a row and every row is an instruction, that the counts in the headings are right, that every directive is written down, that every routine the manuals promise exists, and that the worked examples still assemble to the bytes printed beside them. Documentation goes stale quietly, and this is what stops it.
|
Tests/docs.sh then checks the manuals against the code: that every instruction has a row and every row is an instruction, that the counts in the headings are right, that every directive is written down, that every routine the manuals promise exists, and that the worked examples still assemble to the bytes printed beside them. Documentation goes stale quietly, and this is what stops it.
|
||||||
|
|
||||||
Tests are defined in Tests/manifest, one line per program. To record the current output as the expected result, after you have checked that it is correct:
|
Tests are defined in Tests/manifest, one line per program. To record the current output as the expected result, after you have checked that it is correct:
|
||||||
|
|||||||
@@ -166,8 +166,18 @@ int loadFile(intermediateElement **intermediateArray, char *fileName, int *inter
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
// Read off tokens.
|
// Read off tokens.
|
||||||
while (readToken(&(*intermediateArray)[*intermediateIndex], file, &lineNumber)) {
|
//
|
||||||
if ((size_t)*intermediateIndex >= *arraySize - 1) {
|
// ROOM IS MADE BEFORE THE TOKEN IS READ, not after. readToken writes into the element
|
||||||
|
// at the current index, so a check that came afterwards was checking whether the write
|
||||||
|
// that had already happened was allowed to. It survived for a long time because the
|
||||||
|
// margin usually covered it, and stopped surviving when a file grew past a doubling:
|
||||||
|
// several paths below take a SECOND element for one token - an #Include takes one for
|
||||||
|
// the file name, #Align and #Reserve take one for the count - so the index can move by
|
||||||
|
// two in an iteration and step straight over a margin of one.
|
||||||
|
//
|
||||||
|
// The margin is two for that reason, which is the most any one iteration uses.
|
||||||
|
while (1) {
|
||||||
|
if ((size_t)*intermediateIndex + 2 >= *arraySize) {
|
||||||
size_t grownSize = *arraySize * 2; // Double the size of the array.
|
size_t grownSize = *arraySize * 2; // Double the size of the array.
|
||||||
// Into a temporary, so that the old allocation is still ours to free if
|
// Into a temporary, so that the old allocation is still ours to free if
|
||||||
// this fails, rather than being lost the moment realloc returns NULL.
|
// this fails, rather than being lost the moment realloc returns NULL.
|
||||||
@@ -184,7 +194,9 @@ int loadFile(intermediateElement **intermediateArray, char *fileName, int *inter
|
|||||||
*intermediateArray = grown;
|
*intermediateArray = grown;
|
||||||
*arraySize = grownSize;
|
*arraySize = grownSize;
|
||||||
}
|
}
|
||||||
//printf("Token number %d\n", intermediateIndex);
|
if (!readToken(&(*intermediateArray)[*intermediateIndex], file, &lineNumber)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
// Go ahead and mark what we already know about this token.
|
// Go ahead and mark what we already know about this token.
|
||||||
(*intermediateArray)[*intermediateIndex].fileName = fileName;
|
(*intermediateArray)[*intermediateIndex].fileName = fileName;
|
||||||
(*intermediateArray)[*intermediateIndex].lineNumber = lineNumber;
|
(*intermediateArray)[*intermediateIndex].lineNumber = lineNumber;
|
||||||
|
|||||||
@@ -454,9 +454,9 @@ Status bit 1 says the last operation failed: there is no disk, or the block aske
|
|||||||
|
|
||||||
A disk error is not a fault. Faults on this machine mean it cannot continue, and a read that fails is an ordinary thing that happens to working programs on failing media. It is reported so that a program can cope with it, rather than stopping the machine and taking the choice away.
|
A disk error is not a fault. Faults on this machine mean it cannot continue, and a read that fails is an ordinary thing that happens to working programs on failing media. It is reported so that a program can cope with it, rather than stopping the machine and taking the choice away.
|
||||||
|
|
||||||
## Reading The Filesystem:
|
## Reading And Writing The Filesystem:
|
||||||
|
|
||||||
The disk knows blocks and nothing else, so a filesystem is software. Programs/CosmOS/Source/sbfs.asm reads one.
|
The disk knows blocks and nothing else, so a filesystem is software. Programs/CosmOS/Source/sbfs.asm is one.
|
||||||
|
|
||||||
| Routine | Does |
|
| Routine | Does |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -467,11 +467,35 @@ The disk knows blocks and nothing else, so a filesystem is software. Programs/Co
|
|||||||
| sbfsNext | Steps the walk to the next entry in use. Q is zero if there was one. |
|
| sbfsNext | Steps the walk to the next entry in use. Q is zero if there was one. |
|
||||||
| sbfsCreate | Makes a file. DP0 names it, and SbfsFileBlocks with SbfsFileTail say how big it is. Q is zero if it was made, and then SbfsFileStart says where it went. |
|
| sbfsCreate | Makes a file. DP0 names it, and SbfsFileBlocks with SbfsFileTail say how big it is. Q is zero if it was made, and then SbfsFileStart says where it went. |
|
||||||
| sbfsWriteFile | Writes the file that was made, from Data Memory at DP1. |
|
| sbfsWriteFile | Writes the file that was made, from Data Memory at DP1. |
|
||||||
|
| sbfsDelete | DP0 names a file. Frees its entry and its blocks. Q is zero if it went. |
|
||||||
|
| sbfsRename | DP0 is the name a file has, DP1 the name it should have. Q is zero if it was renamed. Refused if something already answers to the new name. |
|
||||||
|
| sbfsSaveFile | DP0 names the file, DP1 is the data, and SbfsFileBlocks with SbfsFileTail say how big it now is. Writes it whether or not it was there before, and whatever size it used to be. |
|
||||||
|
|
||||||
Finding a file and listing what is there are different jobs. sbfsFind searches for one name; sbfsFirst and sbfsNext walk the whole directory, stopping on each entry that is in use and stepping over the free ones. A walk keeps a directory block in SbfsBuffer between calls, so anything else that goes to the disk in the middle of one ends it: take what is wanted out of an entry before asking the disk for anything else.
|
Finding a file and listing what is there are different jobs. sbfsFind searches for one name; sbfsFirst and sbfsNext walk the whole directory, stopping on each entry that is in use and stepping over the free ones. A walk keeps a directory block in SbfsBuffer between calls, so anything else that goes to the disk in the middle of one ends it: take what is wanted out of an entry before asking the disk for anything else.
|
||||||
|
|
||||||
A file's size is settled when it is made, because nothing can grow one afterwards. Files are laid down contiguously, so the block after a file usually belongs to somebody else. A program that does not know how much it will write has to guess high and accept the slack, or build its output elsewhere and make the file once the size is known.
|
A file's size is settled when it is made, because nothing can grow one afterwards. Files are laid down contiguously, so the block after a file usually belongs to somebody else. A program that does not know how much it will write has to guess high and accept the slack, or build its output elsewhere and make the file once the size is known.
|
||||||
|
|
||||||
|
### Saving Something Twice:
|
||||||
|
|
||||||
|
Which is why saving a document is not the same as writing a file, and why sbfsSaveFile exists rather than each tool doing it. A file that has grown will usually not fit where it was, so saving it means putting it somewhere else and letting go of where it was — and **the obvious order is a trap**:
|
||||||
|
|
||||||
|
```
|
||||||
|
delete the old one
|
||||||
|
make a new one <- refused, and the old one is already gone
|
||||||
|
write it
|
||||||
|
```
|
||||||
|
|
||||||
|
A create can be refused for want of a run long enough even on a disk with plenty of free blocks, because free blocks are only useful to a contiguous file when they are next to each other. Done in that order, the first fragmented disk somebody meets eats their work. sbfsSaveFile does it the other way round:
|
||||||
|
|
||||||
|
```
|
||||||
|
make a temporary nothing is lost if there is nowhere to put it
|
||||||
|
write it
|
||||||
|
delete the original only now, once the new one is safely down
|
||||||
|
rename the temporary
|
||||||
|
```
|
||||||
|
|
||||||
|
**That is what renaming is for.** It looks like a convenience and it is the safety mechanism: it is the only one of the three operations that moves no data — a name lives in the directory entry, so renaming writes twenty two bytes into one block — which makes it the only one that can be left until last and relied on not to fail.
|
||||||
|
|
||||||
Finding room is a walk through the directory rather than a lookup, because there is no allocation table. With files laid down contiguously the directory already says which blocks are spoken for, and a second copy of that would be a second thing to keep right. The free count in the superblock is kept up to date but it is a note rather than the truth: it can be worked out again from the directory, and the directory is the one to believe.
|
Finding room is a walk through the directory rather than a lookup, because there is no allocation table. With files laid down contiguously the directory already says which blocks are spoken for, and a second copy of that would be a second thing to keep right. The free count in the superblock is kept up to date but it is a note rather than the truth: it can be worked out again from the directory, and the directory is the one to believe.
|
||||||
|
|
||||||
A file's length is its block count times 256 plus its tail, which is the same as putting the block count in the high byte and the tail in the low one. Nothing pads a file out, so the bytes after the end of one are whatever else happened to be in that block, and it is the reading program's business to stop where the tail says.
|
A file's length is its block count times 256 plus its tail, which is the same as putting the block count in the high byte and the tail in the low one. Nothing pads a file out, so the bytes after the end of one are whatever else happened to be in that block, and it is the reading program's business to stop where the tail says.
|
||||||
@@ -519,6 +543,30 @@ Every routine names the Data Pointer it works through rather than assuming there
|
|||||||
|
|
||||||
readLine cuts a line short if it is longer than the buffer, and then reads the rest of it and throws it away, so that what is left over does not turn up as the next line. ConsoleEndOfInput is set if the console ran out instead of ending a line, and it is cleared at the start of every call, so it always describes the last line read. That is a different thing from an empty line, and a program reading until there is no more has to be able to tell the two apart.
|
readLine cuts a line short if it is longer than the buffer, and then reads the rest of it and throws it away, so that what is left over does not turn up as the next line. ConsoleEndOfInput is set if the console ran out instead of ending a line, and it is cleared at the start of every call, so it always describes the last line read. That is a different thing from an empty line, and a program reading until there is no more has to be able to tell the two apart.
|
||||||
|
|
||||||
|
## What A Program May Ask The System For:
|
||||||
|
|
||||||
|
A loaded program is on its own hardware and can do anything the machine can do — it is a fence, not a wall. But the things it usually wants are things the system is already doing, and asking is both shorter and the only way to reach code that was assembled separately. `CALL` needs a label, and a label has to be in the same assembly; `SWI` needs only a number both sides agree on.
|
||||||
|
|
||||||
|
Those numbers are written down once, in `Programs/CosmOS/Source/services.asm`, which both the system and the program include. Neither side ever types a number.
|
||||||
|
|
||||||
|
| Service | Does |
|
||||||
|
| --- | --- |
|
||||||
|
| osPrintString | DP0 names a string ending in a zero byte. Prints it. |
|
||||||
|
| osReadLine | DP0 names somewhere to put a line, B says how much room there is. Reads one from the console. |
|
||||||
|
| osExit | Gives the machine back. Does not return. |
|
||||||
|
| osArgument | DP0 names somewhere to put whatever followed the run command, B says how much room there is. |
|
||||||
|
|
||||||
|
```
|
||||||
|
#Include services.asm
|
||||||
|
...
|
||||||
|
SETD.0 Message
|
||||||
|
SWI osPrintString
|
||||||
|
```
|
||||||
|
|
||||||
|
`osArgument` is how a program is told what it is for. Everything written before it did the same thing however it was started, which is fine for a program that greets you and no use to one that edits a named document. What arrives is the whole rest of the line, spaces and all, rather than a list of words: what counts as an argument is the program's business, and handing over what was typed is the system's.
|
||||||
|
|
||||||
|
A handler is entered with the caller's registers exactly as they were, because an interrupt frame is pushed rather than cleared. That is why a service can be given a pointer in DP0 and a count in B without any of it being copied anywhere first.
|
||||||
|
|
||||||
## Loading A Program From A Disk:
|
## Loading A Program From A Disk:
|
||||||
|
|
||||||
A program that was not the one the machine booted from carries sixteen bytes in front of it saying where it belongs.
|
A program that was not the one the machine booted from carries sixteen bytes in front of it saying where it belongs.
|
||||||
@@ -571,6 +619,26 @@ That is not general placement: a base applies to a whole segment, and only the f
|
|||||||
|
|
||||||
Whoever does the loading keeps its own code and data below the addresses the loaded program claims. That is an arrangement between the two of them rather than anything the machine enforces. Programs/CosmOS is where that arrangement is written down as a memory map and kept to.
|
Whoever does the loading keeps its own code and data below the addresses the loaded program claims. That is an arrangement between the two of them rather than anything the machine enforces. Programs/CosmOS is where that arrangement is written down as a memory map and kept to.
|
||||||
|
|
||||||
|
## Programs That Come With The System:
|
||||||
|
|
||||||
|
`Programs/CosmOS/Apps` holds what the shell can load. Most of them are old programs that were written for the bare machine and needed five edits to become loadable ones; the last three were written for the system as it is now.
|
||||||
|
|
||||||
|
| Program | What it is for |
|
||||||
|
| --- | --- |
|
||||||
|
| Life | Conway's Game of Life, which had to be taught to stop, since a program that never ends takes the shell with it. Polls the console between generations. |
|
||||||
|
| Snake | A game. Draws a whole screen with cursor addressing and steers with single keys, asking the console once a frame and never waiting. |
|
||||||
|
| Keys | The console interrupting rather than being asked. The only one that brings a vector of its own, which is what the version two format exists for. |
|
||||||
|
| Say | Prints whatever it was told, which is the shortest thing that shows osArgument working. |
|
||||||
|
| Edit | A line editor. |
|
||||||
|
|
||||||
|
### The Editor:
|
||||||
|
|
||||||
|
`Edit` is the first program on this machine that makes a file a person typed — every byte on every disk before it was put there by the host tool. It is line oriented in the manner of `ed`: `l` lists, `a` adds at the end, `i` and `c` and `d` take a line number, `w` writes and `q` stops.
|
||||||
|
|
||||||
|
It keeps the document as a **linked list of lines** rather than one buffer with newlines in it. Each line says where the next one is, how long it is, and then its bytes. Inserting is two pointers changed and nothing moved; with a flat buffer it would mean shifting every byte after the edit, on a machine whose only block move is a device asked politely. The price is that deleted lines are not reused, so a heavy session uses more room than the document needs and writing it out is what tidies up.
|
||||||
|
|
||||||
|
Saving goes through `sbfsSaveFile`, so a document that has grown is written somewhere else and the original is only let go of once the new one is safely down. That is the whole reason the editor was written: not because the machine needed an editor, but because every tool that produces a file needs the same four operations, and building them for one imaginary tool is how they end up wrong.
|
||||||
|
|
||||||
## Refusing:
|
## Refusing:
|
||||||
|
|
||||||
A device can refuse what it was asked to do. This is not the same as interrupting. An interrupt is a device asking for attention later, answered between instructions once the CPU is ready. A refusal is a device saying no to the instruction happening now, so the machine stops where it stands rather than carrying on as though the access had worked.
|
A device can refuse what it was asked to do. This is not the same as interrupting. An interrupt is a device asking for attention later, answered between instructions once the CPU is ready. A refusal is a device saying no to the instruction happening now, so the machine stops where it stands rather than carrying on as though the access had worked.
|
||||||
|
|||||||
+18
-1
@@ -179,6 +179,23 @@ else:
|
|||||||
problems.append("%s is bit %d of the console status port and The Console does"
|
problems.append("%s is bit %d of the console status port and The Console does"
|
||||||
" not mention it" % (name, bit))
|
" not mention it" % (name, bit))
|
||||||
|
|
||||||
|
# ---- Every service the system offers has a row ----
|
||||||
|
#
|
||||||
|
# services.asm is the one place the numbers are written, and both the system and every
|
||||||
|
# program include it. A service added there and not here is one nothing can find out about
|
||||||
|
# except by reading the source of the operating system.
|
||||||
|
services = read("Programs/CosmOS/Source/services.asm")
|
||||||
|
offered = re.findall(r'^\s{2}(os[A-Za-z]+)\s+0d\d+', services, re.M)
|
||||||
|
if not offered:
|
||||||
|
problems.append("no services could be found in services.asm")
|
||||||
|
elif "## What A Program May Ask The System For:" not in pm:
|
||||||
|
problems.append("the Programming Manual has lost its services section")
|
||||||
|
else:
|
||||||
|
section = pm.split("## What A Program May Ask The System For:")[1].split("\n## ")[0]
|
||||||
|
for name in offered:
|
||||||
|
if ("| %s |" % name) not in section:
|
||||||
|
problems.append("%s is a service and has no row in the services table" % name)
|
||||||
|
|
||||||
# ---- Every directive the assembler knows is written down ----
|
# ---- Every directive the assembler knows is written down ----
|
||||||
for directive in sorted(set(re.findall(r'"(#[A-Za-z]+)"', util))):
|
for directive in sorted(set(re.findall(r'"(#[A-Za-z]+)"', util))):
|
||||||
if directive not in am:
|
if directive not in am:
|
||||||
@@ -189,7 +206,7 @@ for directive in sorted(set(re.findall(r'"(#[A-Za-z]+)"', util))):
|
|||||||
# The first column of the table in each of these sections names something the library has
|
# The first column of the table in each of these sections names something the library has
|
||||||
# to define. A routine renamed in the source and not in the manual is caught here, which
|
# to define. A routine renamed in the source and not in the manual is caught here, which
|
||||||
# is what keeps the tables a description rather than a memory.
|
# is what keeps the tables a description rather than a memory.
|
||||||
for heading, library in [("## Reading The Filesystem:", "Programs/CosmOS/Source/sbfs.asm"),
|
for heading, library in [("## Reading And Writing The Filesystem:", "Programs/CosmOS/Source/sbfs.asm"),
|
||||||
("## The Console Library:", "Programs/CosmOS/Source/console.asm")]:
|
("## The Console Library:", "Programs/CosmOS/Source/console.asm")]:
|
||||||
if heading not in pm:
|
if heading not in pm:
|
||||||
problems.append("the Programming Manual has lost its \"%s\" section"
|
problems.append("the Programming Manual has lost its \"%s\" section"
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
0000 0001 0001 0002 0003 0005 0008 000D 0015 0022 0037 0059 0090 00E9 0179 0262 03DB 063D 0A18 1055 1A6D 2AC2 452F 6FF1
|
0000 0001 0001 0002 0003 0005 0008 000D 0015 0022 0037 0059 0090 00E9 0179 0262 03DB 063D 0A18 1055 1A6D 2AC2 452F 6FF1
|
||||||
Execution halted after 2534 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -222,5 +222,5 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
Execution stopped after 3000000 cycles. (cycle limit reached)
|
Execution stopped. (cycle limit reached)
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -222,5 +222,5 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
Execution stopped after 3000000 cycles. (cycle limit reached)
|
Execution stopped. (cycle limit reached)
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
00000000 00000001 00000001 00000002 00000003 00000005 00000008 0000000D 00000015 00000022 00000037 00000059 00000090 000000E9 00000179 00000262 000003DB 0000063D 00000A18 00001055 00001A6D 00002AC2 0000452F 00006FF1 0000B520 00012511 0001DA31 0002FF42 0004D973 0007D8B5 000CB228 00148ADD 00213D05 0035C7E2 005704E7 008CCCC9 00E3D1B0 01709E79 02547029 03C50EA2 06197ECB 09DE8D6D 0FF80C38 19D699A5 29CEA5DD 43A53F82 6D73E55F
|
00000000 00000001 00000001 00000002 00000003 00000005 00000008 0000000D 00000015 00000022 00000037 00000059 00000090 000000E9 00000179 00000262 000003DB 0000063D 00000A18 00001055 00001A6D 00002AC2 0000452F 00006FF1 0000B520 00012511 0001DA31 0002FF42 0004D973 0007D8B5 000CB228 00148ADD 00213D05 0035C7E2 005704E7 008CCCC9 00E3D1B0 01709E79 02547029 03C50EA2 06197ECB 09DE8D6D 0FF80C38 19D699A5 29CEA5DD 43A53F82 6D73E55F
|
||||||
Execution halted after 10610 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
0 1 1 2 3 5 8 13 21 34 55 89 144 233
|
0 1 1 2 3 5 8 13 21 34 55 89 144 233
|
||||||
Execution halted after 1506 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251
|
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251
|
||||||
Execution halted after 54061 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Fault: The device on port 233 refused the access at Program Address 0x001D, and nothing is installed to deal with it.
|
Fault: The device on port 233 refused the access at Program Address 0x001D, and nothing is installed to deal with it.
|
||||||
O
|
O
|
||||||
Execution halted after 16 cycles.
|
Execution halted.
|
||||||
[exit 1]
|
[exit 1]
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ blitted
|
|||||||
DE AD
|
DE AD
|
||||||
untouched
|
untouched
|
||||||
ABABCDEFGH
|
ABABCDEFGH
|
||||||
Execution halted after 464 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
QAB C qab c
|
QAB C qab c
|
||||||
9876543210
|
9876543210
|
||||||
Execution halted after 176 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ asked for: 0D ready keys interrupts
|
|||||||
what arrived:
|
what arrived:
|
||||||
keys
|
keys
|
||||||
at the end: 02 ended
|
at the end: 02 ended
|
||||||
Execution halted after 1597 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ hi
|
|||||||
|
|
||||||
at the end: 06 ended keys
|
at the end: 06 ended keys
|
||||||
line mode: 02 ended
|
line mode: 02 ended
|
||||||
Execution halted after 891 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -10,5 +10,5 @@ lines read:
|
|||||||
[SplitBit] 8
|
[SplitBit] 8
|
||||||
[a line that is f] 16
|
[a line that is f] 16
|
||||||
end of input
|
end of input
|
||||||
Execution halted after 6064 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
DE AD BE EF
|
DE AD BE EF
|
||||||
01 FF
|
01 FF
|
||||||
00 00
|
00 00
|
||||||
Execution halted after 315 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ AA BB
|
|||||||
readonly
|
readonly
|
||||||
nobank
|
nobank
|
||||||
done
|
done
|
||||||
Execution halted after 245 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
CosmOS
|
CosmOS
|
||||||
> dir list what is on the disk
|
> dir list what is on the disk
|
||||||
load <file> read a program off 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
|
||||||
dump sixty four bytes of memory, and again for more
|
dump sixty four bytes of memory, and again for more
|
||||||
dump <program|data|bank> <address>
|
dump <program|data|bank> <address>
|
||||||
help this
|
help this
|
||||||
@@ -21,5 +23,5 @@ aName22CharactersLong! 22
|
|||||||
12 files
|
12 files
|
||||||
> > I do not know: frobnicate
|
> > I do not know: frobnicate
|
||||||
> halted
|
> halted
|
||||||
Execution halted after 12376 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -19,5 +19,5 @@ CosmOS
|
|||||||
0020 00 FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 ................
|
0020 00 FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 ................
|
||||||
0030 00 FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 ................
|
0030 00 FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 ................
|
||||||
> halted
|
> halted
|
||||||
Execution halted after 23512 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
CosmOS
|
||||||
|
> loaded, starting at 2000
|
||||||
|
> poem.txt, 0 lines
|
||||||
|
> : : : : > : : > 1: alpha
|
||||||
|
2: INSERTED
|
||||||
|
3: beta
|
||||||
|
4: gamma
|
||||||
|
> : > > 1: CHANGED
|
||||||
|
2: INSERTED
|
||||||
|
3: beta
|
||||||
|
> written, 22 bytes
|
||||||
|
> finished
|
||||||
|
> poem.txt, 3 lines
|
||||||
|
> 1: CHANGED
|
||||||
|
2: INSERTED
|
||||||
|
3: beta
|
||||||
|
> there is no such line
|
||||||
|
> finished
|
||||||
|
> halted
|
||||||
|
Execution halted.
|
||||||
|
[exit 0]
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
CosmOS
|
||||||
|
> one.txt 13
|
||||||
|
two.txt 14
|
||||||
|
2 files
|
||||||
|
> renamed
|
||||||
|
> first.txt 13
|
||||||
|
two.txt 14
|
||||||
|
2 files
|
||||||
|
> gone
|
||||||
|
> two.txt 14
|
||||||
|
1 file
|
||||||
|
> no such file
|
||||||
|
> rename what to what?
|
||||||
|
> there is no such file, or that name is taken
|
||||||
|
> halted
|
||||||
|
Execution halted.
|
||||||
|
[exit 0]
|
||||||
@@ -5,5 +5,5 @@ finished
|
|||||||
> Hello, World!
|
> Hello, World!
|
||||||
finished
|
finished
|
||||||
> halted
|
> halted
|
||||||
Execution halted after 2702 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -13,5 +13,5 @@ FE10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
|||||||
FE20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
FE20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||||
FE30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
FE30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||||
> halted
|
> halted
|
||||||
Execution halted after 10055 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -869,5 +869,5 @@ the board has settled
|
|||||||
finished
|
finished
|
||||||
>
|
>
|
||||||
halted
|
halted
|
||||||
Execution halted after 11671230 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -21,5 +21,5 @@ stopped
|
|||||||
finished
|
finished
|
||||||
> >
|
> >
|
||||||
halted
|
halted
|
||||||
Execution halted after 26243 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ no filesystem on the disk
|
|||||||
> no filesystem on the disk
|
> no filesystem on the disk
|
||||||
>
|
>
|
||||||
halted
|
halted
|
||||||
Execution halted after 636 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ hello.sbx 52
|
|||||||
Life.sbx 1411
|
Life.sbx 1411
|
||||||
Snake.sbx 2175
|
Snake.sbx 2175
|
||||||
Keys.sbx 663
|
Keys.sbx 663
|
||||||
|
Say.sbx 155
|
||||||
notes.txt 21
|
notes.txt 21
|
||||||
6 files
|
7 files
|
||||||
> load what?
|
> load what?
|
||||||
> no such file
|
> no such file
|
||||||
> not a program
|
> not a program
|
||||||
@@ -18,5 +19,5 @@ finished
|
|||||||
what should I call you? hello, Claude. that is all I do.
|
what should I call you? hello, Claude. that is all I do.
|
||||||
finished
|
finished
|
||||||
> halted
|
> halted
|
||||||
Execution halted after 14083 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
CosmOS
|
||||||
|
> loaded, starting at 2000
|
||||||
|
> nothing was said
|
||||||
|
finished
|
||||||
|
> it says: notes.txt
|
||||||
|
finished
|
||||||
|
> it says: a longer thing with spaces
|
||||||
|
finished
|
||||||
|
> halted
|
||||||
|
Execution halted.
|
||||||
|
[exit 0]
|
||||||
@@ -289,5 +289,5 @@ you ran into something
|
|||||||
finished
|
finished
|
||||||
>
|
>
|
||||||
halted
|
halted
|
||||||
Execution halted after 1910693 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
ABCZ
|
ABCZ
|
||||||
Execution halted after 21 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
O
|
O
|
||||||
K
|
K
|
||||||
!
|
!
|
||||||
Execution halted after 19 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
04
|
04
|
||||||
04
|
04
|
||||||
06
|
06
|
||||||
Execution halted after 114 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
00
|
00
|
||||||
from the disk
|
from the disk
|
||||||
02
|
02
|
||||||
Execution halted after 245 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ one
|
|||||||
two
|
two
|
||||||
three
|
three
|
||||||
done
|
done
|
||||||
Execution halted after 126 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
O
|
O
|
||||||
K
|
K
|
||||||
Execution halted after 17 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
Fault: 0xFE at Program Address 0x0002 is not an instruction.
|
Fault: 0xFE at Program Address 0x0002 is not an instruction.
|
||||||
Execution halted after 2 cycles.
|
Execution halted.
|
||||||
[exit 1]
|
[exit 1]
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ caught 01FC
|
|||||||
read ok
|
read ok
|
||||||
lowered ok
|
lowered ok
|
||||||
fenced 05
|
fenced 05
|
||||||
Execution halted after 535 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
Hello, World!
|
Hello, World!
|
||||||
Execution halted after 70 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Input Test: Will echo anything you put in.
|
Input Test: Will echo anything you put in.
|
||||||
Hello SplitBit
|
Hello SplitBit
|
||||||
Execution halted after 406 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Input Test: Will echo anything you put in.
|
Input Test: Will echo anything you put in.
|
||||||
Hello SplitBit
|
Hello SplitBit
|
||||||
Execution halted after 406 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
00 00 00 00 01
|
00 00 00 00 01
|
||||||
1
|
1
|
||||||
Execution halted after 4707 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
ready
|
ready
|
||||||
trap
|
trap
|
||||||
device
|
device
|
||||||
Execution halted after 105 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
OKC
|
OKC
|
||||||
Execution halted after 19 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
loader
|
loader
|
||||||
loaded off a disk, with a string and a loop of its own
|
loaded off a disk, with a string and a loop of its own
|
||||||
Execution halted after 1098 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
before
|
before
|
||||||
loaded
|
loaded
|
||||||
back
|
back
|
||||||
Execution halted after 132 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Fault: The device on port 16 interrupted at Program Address 0x0019, and hardware vector 16 has no handler installed.
|
Fault: The device on port 16 interrupted at Program Address 0x0019, and hardware vector 16 has no handler installed.
|
||||||
M
|
M
|
||||||
S
|
S
|
||||||
Execution halted after 16 cycles.
|
Execution halted.
|
||||||
[exit 1]
|
[exit 1]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
0000 0032
|
0000 0032
|
||||||
Execution halted after 1134 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
AAA
|
AAA
|
||||||
Execution halted after 24 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
00 31
|
00 31
|
||||||
Execution halted after 78 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ twenty
|
|||||||
sixty three
|
sixty three
|
||||||
automatic
|
automatic
|
||||||
done
|
done
|
||||||
Execution halted after 272 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Hello
|
Hello
|
||||||
World
|
World
|
||||||
H
|
H
|
||||||
Execution halted after 79 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Hello, World!
|
Hello, World!
|
||||||
42 is the great answer.
|
42 is the great answer.
|
||||||
Execution halted after 295 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ Testing printByteHex...
|
|||||||
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
|
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
|
||||||
|
|
||||||
Testing complete!
|
Testing complete!
|
||||||
Execution halted after 71185 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Fault: The device on port 17 refused the access at Program Address 0x000A, and nothing is installed to deal with it.
|
Fault: The device on port 17 refused the access at Program Address 0x000A, and nothing is installed to deal with it.
|
||||||
O
|
O
|
||||||
Execution halted after 6 cycles.
|
Execution halted.
|
||||||
[exit 1]
|
[exit 1]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
caught
|
caught
|
||||||
caught
|
caught
|
||||||
done
|
done
|
||||||
Execution halted after 136 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ flags 01
|
|||||||
5A 5A
|
5A 5A
|
||||||
refused
|
refused
|
||||||
refused
|
refused
|
||||||
Execution halted after 443 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
10 10 00
|
10 10 00
|
||||||
FF 01 00
|
FF 01 00
|
||||||
05 00 00
|
05 00 00
|
||||||
Execution halted after 440 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
here.txt 0002 already here
|
||||||
|
doc.txt 0003 first draft
|
||||||
|
doc.txt 0004 a second draft, which is longer than the first
|
||||||
|
notes.txt 0004 a second draft, which is longer than the first
|
||||||
|
doc.txt gone
|
||||||
|
notes.txt gone
|
||||||
|
Execution halted.
|
||||||
|
[exit 0]
|
||||||
@@ -3,5 +3,5 @@ across.txt 0002 BC ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHI
|
|||||||
aName22CharactersLong! 0000 16 exactly twenty two!!!!
|
aName22CharactersLong! 0000 16 exactly twenty two!!!!
|
||||||
empty.txt 0000 00
|
empty.txt 0000 00
|
||||||
absent.txt missing
|
absent.txt missing
|
||||||
Execution halted after 19230 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ across.txt 700
|
|||||||
empty.txt 0
|
empty.txt 0
|
||||||
aName22CharactersLong! 22
|
aName22CharactersLong! 22
|
||||||
files: 12
|
files: 12
|
||||||
Execution halted after 9498 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
here.txt 0002 already here
|
here.txt 0002 already here
|
||||||
first.txt 0003 written by SplitBit itself
|
first.txt 0003 written by SplitBit itself
|
||||||
second.txt 0004 and a second one after it
|
second.txt 0004 and a second one after it
|
||||||
Execution halted after 5888 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
OK
|
OK
|
||||||
Execution halted after 14 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ reclaimed: FFFF
|
|||||||
still good: 33
|
still good: 33
|
||||||
borrowed: 7 and 9
|
borrowed: 7 and 9
|
||||||
back home: FFFF
|
back home: FFFF
|
||||||
Execution halted after 1054 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ one
|
|||||||
two
|
two
|
||||||
three
|
three
|
||||||
AFTER
|
AFTER
|
||||||
Execution halted after 122 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Fault: Software vector 20, dispatched from Program Address 0x0008, has no handler installed.
|
Fault: Software vector 20, dispatched from Program Address 0x0008, has no handler installed.
|
||||||
O
|
O
|
||||||
Execution halted after 5 cycles.
|
Execution halted.
|
||||||
[exit 1]
|
[exit 1]
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ same: yes no no no
|
|||||||
hex: 2000 00FF 00FF BEEF FFFF 0000
|
hex: 2000 00FF 00FF BEEF FFFF 0000
|
||||||
0 is a fine way to begin a string
|
0 is a fine way to begin a string
|
||||||
no number here
|
no number here
|
||||||
Execution halted after 2730 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
Two pointers, no stack shenanigans.
|
Two pointers, no stack shenanigans.
|
||||||
Execution halted after 395 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
OK!
|
OK!
|
||||||
good
|
good
|
||||||
AFTER
|
AFTER
|
||||||
Execution halted after 77 cycles.
|
Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
load Edit.sbx
|
||||||
|
run poem.txt
|
||||||
|
a
|
||||||
|
alpha
|
||||||
|
beta
|
||||||
|
gamma
|
||||||
|
.
|
||||||
|
i 2
|
||||||
|
INSERTED
|
||||||
|
.
|
||||||
|
l
|
||||||
|
c 1
|
||||||
|
CHANGED
|
||||||
|
d 4
|
||||||
|
l
|
||||||
|
w
|
||||||
|
q
|
||||||
|
run poem.txt
|
||||||
|
l
|
||||||
|
d 99
|
||||||
|
q
|
||||||
|
exit
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
dir
|
||||||
|
rename one.txt first.txt
|
||||||
|
dir
|
||||||
|
delete first.txt
|
||||||
|
dir
|
||||||
|
delete first.txt
|
||||||
|
rename two.txt
|
||||||
|
rename two.txt two.txt
|
||||||
|
exit
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
load Say.sbx
|
||||||
|
run
|
||||||
|
run notes.txt
|
||||||
|
run a longer thing with spaces
|
||||||
|
exit
|
||||||
+32
-1
@@ -59,7 +59,7 @@ for i in 1 2 3 4 5 6 7 8; do "$TOOL" put "$DISKS/sbfs.img" "filler$i.txt" >/dev/
|
|||||||
# to the hardware, so something has to check that one still gives the machine back.
|
# to the hardware, so something has to check that one still gives the machine back.
|
||||||
#
|
#
|
||||||
# notes.txt is there so that loading something that is not a program can be tried too.
|
# notes.txt is there so that loading something that is not a program can be tried too.
|
||||||
"$TOOL" format "$DISKS/cosmos.img" 32 1 >/dev/null
|
"$TOOL" format "$DISKS/cosmos.img" 256 2 >/dev/null
|
||||||
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
||||||
"$ROOT/Programs/CosmOS/Apps/greet.asm" -o "$WORK/greet.sbx" >/dev/null
|
"$ROOT/Programs/CosmOS/Apps/greet.asm" -o "$WORK/greet.sbx" >/dev/null
|
||||||
"$TOOL" put "$DISKS/cosmos.img" "$WORK/greet.sbx" >/dev/null
|
"$TOOL" put "$DISKS/cosmos.img" "$WORK/greet.sbx" >/dev/null
|
||||||
@@ -86,6 +86,11 @@ for i in 1 2 3 4 5 6 7 8; do "$TOOL" put "$DISKS/sbfs.img" "filler$i.txt" >/dev/
|
|||||||
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
|
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
|
||||||
"$ROOT/Programs/CosmOS/Apps/Keys.asm" -o "$WORK/Keys.sbx" >/dev/null
|
"$ROOT/Programs/CosmOS/Apps/Keys.asm" -o "$WORK/Keys.sbx" >/dev/null
|
||||||
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Keys.sbx" >/dev/null
|
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Keys.sbx" >/dev/null
|
||||||
|
# Say.sbx is the first program that can be told anything. Everything before it did the same
|
||||||
|
# thing however it was started.
|
||||||
|
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
||||||
|
"$ROOT/Programs/CosmOS/Apps/Say.asm" -o "$WORK/Say.sbx" >/dev/null
|
||||||
|
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Say.sbx" >/dev/null
|
||||||
printf 'this is not a program' > notes.txt
|
printf 'this is not a program' > notes.txt
|
||||||
"$TOOL" put "$DISKS/cosmos.img" notes.txt >/dev/null
|
"$TOOL" put "$DISKS/cosmos.img" notes.txt >/dev/null
|
||||||
|
|
||||||
@@ -94,3 +99,29 @@ printf 'this is not a program' > notes.txt
|
|||||||
"$TOOL" format "$DISKS/write.img" 32 1 >/dev/null
|
"$TOOL" format "$DISKS/write.img" 32 1 >/dev/null
|
||||||
printf 'already here' > here.txt
|
printf 'already here' > here.txt
|
||||||
"$TOOL" put "$DISKS/write.img" here.txt >/dev/null
|
"$TOOL" put "$DISKS/write.img" here.txt >/dev/null
|
||||||
|
|
||||||
|
# A disk of its own for the editing test, which deletes and renames things and would
|
||||||
|
# otherwise leave the writing test's disk looking nothing like the writing test expects.
|
||||||
|
# It starts with one file on it so that a document written here has to be placed around
|
||||||
|
# something, and so that the disk can be compared afterwards against one that never had
|
||||||
|
# any of it: the metadata should come back exactly as it was.
|
||||||
|
"$TOOL" format "$DISKS/edit.img" 32 1 >/dev/null
|
||||||
|
"$TOOL" put "$DISKS/edit.img" here.txt >/dev/null
|
||||||
|
|
||||||
|
# A disk for the shell's delete and rename, which is its own because those change what is
|
||||||
|
# on it. A fixture whose name has a directory in it is used as it stands rather than being
|
||||||
|
# made fresh per test, so a test that writes to a shared one would quietly change what
|
||||||
|
# every test after it sees.
|
||||||
|
"$TOOL" format "$DISKS/files.img" 32 1 >/dev/null
|
||||||
|
printf 'the first one' > one.txt
|
||||||
|
printf 'the second one' > two.txt
|
||||||
|
"$TOOL" put "$DISKS/files.img" one.txt >/dev/null
|
||||||
|
"$TOOL" put "$DISKS/files.img" two.txt >/dev/null
|
||||||
|
|
||||||
|
# A disk for the editor, holding nothing but the editor. Its own, because the whole point
|
||||||
|
# of it is that it writes: a document made on a shared fixture would turn up in the file
|
||||||
|
# listing of every test that came after it.
|
||||||
|
"$TOOL" format "$DISKS/editor.img" 256 2 >/dev/null
|
||||||
|
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
|
||||||
|
"$ROOT/Programs/CosmOS/Apps/Edit.asm" -o "$WORK/Edit.sbx" >/dev/null
|
||||||
|
"$TOOL" put "$DISKS/editor.img" "$WORK/Edit.sbx" >/dev/null
|
||||||
|
|||||||
@@ -77,6 +77,11 @@ sbfsWalkTest | testPrograms/sbfsWalkTest.asm | run | -
|
|||||||
# Writing a filesystem, then reading back what was written. The disk starts with a file
|
# Writing a filesystem, then reading back what was written. The disk starts with a file
|
||||||
# on it, so allocation has to find room rather than start at the beginning.
|
# on it, so allocation has to find room rather than start at the beginning.
|
||||||
sbfsWriteTest | testPrograms/sbfsWriteTest.asm | run | - | - | disks/write.img
|
sbfsWriteTest | testPrograms/sbfsWriteTest.asm | run | - | - | disks/write.img
|
||||||
|
# Deleting, renaming, and saving over something already there, which is what any tool that
|
||||||
|
# edits a document needs and what none of the tests above touch. The interesting line is
|
||||||
|
# the third: saving a longer version moves the file, because files here are contiguous and
|
||||||
|
# do not grow. The last two lines are a rename and a delete having actually happened.
|
||||||
|
sbfsEditTest | testPrograms/sbfsEditTest.asm | run | - | - | disks/edit.img
|
||||||
|
|
||||||
# Loading a program off a disk and running it. Everything below this line existed before
|
# Loading a program off a disk and running it. Everything below this line existed before
|
||||||
# the loader did; the only new part is the sixteen bytes on the front of a loadable
|
# the loader did; the only new part is the sixteen bytes on the front of a loadable
|
||||||
@@ -238,6 +243,26 @@ cosmosSnake | CosmOS/Source/cosmos.asm | run | cosmosSna
|
|||||||
# console's slot at FE00 is zero again, and CosmOS's own disk handler further along is
|
# console's slot at FE00 is zero again, and CosmOS's own disk handler further along is
|
||||||
# untouched by a program having installed over the top of it.
|
# untouched by a program having installed over the top of it.
|
||||||
cosmosKeys | CosmOS/Source/cosmos.asm | run | cosmosKeys.in | - | disks/cosmos.img
|
cosmosKeys | CosmOS/Source/cosmos.asm | run | cosmosKeys.in | - | disks/cosmos.img
|
||||||
|
# The shell taking things off a disk and calling them something else, which is the last of
|
||||||
|
# CosmOS's original four verbs to be built and the first time anything has changed a disk
|
||||||
|
# from the shell. Its own image, because it changes what is on it: a fixture named with a
|
||||||
|
# directory is used as it stands, so a test that writes to a shared one changes what every
|
||||||
|
# test after it sees. The refusals are here too - deleting what is not there, renaming with
|
||||||
|
# a name missing, and renaming something to a name already taken.
|
||||||
|
cosmosFiles | CosmOS/Source/cosmos.asm | run | cosmosFiles.in | - | disks/files.img
|
||||||
|
# A program being told what it is for. Nothing loaded before this could be told anything,
|
||||||
|
# so every app did the same thing however it was started - which is fine for a demo and no
|
||||||
|
# use at all to a tool that edits a named document. Run three times: with nothing, with a
|
||||||
|
# name, and with several words, since what arrives is the rest of the line rather than a
|
||||||
|
# list and it is the program's business what to make of it.
|
||||||
|
cosmosSay | CosmOS/Source/cosmos.asm | run | cosmosSay.in | - | disks/cosmos.img
|
||||||
|
# The editor, which is the first program on this machine that makes a file a person typed:
|
||||||
|
# every byte on every other image here was put there by the host tool. It is run twice in
|
||||||
|
# one session, and that is the test rather than a flourish - the second run reads back what
|
||||||
|
# the first one wrote, so the whole path is checked at once: read a name from the argument,
|
||||||
|
# split a file into lines, edit them, build a file back out of them, and save it over
|
||||||
|
# something that was already there and is now a different size.
|
||||||
|
cosmosEdit | CosmOS/Source/cosmos.asm | run | cosmosEdit.in | - | disks/editor.img
|
||||||
# The programs CosmOS loads, checked on their own so that a failure here reads as "the app
|
# The programs CosmOS loads, checked on their own so that a failure here reads as "the app
|
||||||
# does not assemble" rather than as a broken disk image.
|
# does not assemble" rather than as a broken disk image.
|
||||||
app-greet | CosmOS/Apps/greet.asm | assemble | - | -
|
app-greet | CosmOS/Apps/greet.asm | assemble | - | -
|
||||||
@@ -245,6 +270,8 @@ app-hello | CosmOS/Apps/hello.asm | assemble | -
|
|||||||
app-Life | CosmOS/Apps/Life.asm | assemble | - | -
|
app-Life | CosmOS/Apps/Life.asm | assemble | - | -
|
||||||
app-Snake | CosmOS/Apps/Snake.asm | assemble | - | -
|
app-Snake | CosmOS/Apps/Snake.asm | assemble | - | -
|
||||||
app-Keys | CosmOS/Apps/Keys.asm | assemble | - | -
|
app-Keys | CosmOS/Apps/Keys.asm | assemble | - | -
|
||||||
|
app-Say | CosmOS/Apps/Say.asm | assemble | - | -
|
||||||
|
app-Edit | CosmOS/Apps/Edit.asm | assemble | - | -
|
||||||
|
|
||||||
# ---- Programs driven by console input ----
|
# ---- Programs driven by console input ----
|
||||||
inputTest | inputTest.asm | run | inputTest.in | -
|
inputTest | inputTest.asm | run | inputTest.in | -
|
||||||
|
|||||||
@@ -84,6 +84,24 @@ trim() {
|
|||||||
printf '%s' "$v"
|
printf '%s' "$v"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Takes the cycle count out of the emulator's last line, in place.
|
||||||
|
#
|
||||||
|
# HOW MANY CYCLES A PROGRAM TOOK IS NOT WHAT ANY OF THESE TESTS ARE ABOUT, and having it in
|
||||||
|
# every recorded result made every one of them fragile in the same way: two instructions
|
||||||
|
# added to CosmOS moved the count in six unrelated files at once, so a real difference
|
||||||
|
# would have arrived in a crowd of meaningless ones and had to be picked out by hand.
|
||||||
|
#
|
||||||
|
# WHETHER a program stopped on its own or ran into its limit is kept, because that is
|
||||||
|
# behaviour and several tests exist to check it. Only the number goes.
|
||||||
|
#
|
||||||
|
# Anything that genuinely wants to measure cycles should say so out loud in a test of its
|
||||||
|
# own rather than every test carrying the measurement and nothing asserting anything about
|
||||||
|
# it.
|
||||||
|
settle() {
|
||||||
|
sed -i -E 's/^Execution halted after [0-9]+ cycles\.$/Execution halted./;
|
||||||
|
s/^Execution stopped after [0-9]+ cycles\. \(cycle limit reached\)$/Execution stopped. (cycle limit reached)/' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
check() {
|
check() {
|
||||||
# check <name> <actual-file>
|
# check <name> <actual-file>
|
||||||
local name="$1" actual="$2" golden="$EXPECTED/$1.out"
|
local name="$1" actual="$2" golden="$EXPECTED/$1.out"
|
||||||
@@ -205,6 +223,7 @@ while IFS='|' read -r name src mode stdin limit disk; do
|
|||||||
# supposed to exit non zero, and that should be just as pinned down as
|
# supposed to exit non zero, and that should be just as pinned down as
|
||||||
# what it printed.
|
# what it printed.
|
||||||
printf '[exit %d]\n' "$STATUS" >> "$OUT"
|
printf '[exit %d]\n' "$STATUS" >> "$OUT"
|
||||||
|
settle "$OUT"
|
||||||
check "$name" "$OUT"
|
check "$name" "$OUT"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
|||||||
Executable
+259
@@ -0,0 +1,259 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Checks the things a recorded output cannot see.
|
||||||
|
#
|
||||||
|
# Every other test in this suite pipes standard input in and standard output to a file, and
|
||||||
|
# compares what came out against what came out last time. That answers "what does this
|
||||||
|
# program print", which is the right question almost always, and it is blind to two whole
|
||||||
|
# classes of behaviour:
|
||||||
|
#
|
||||||
|
# WHEN something is printed. Piped output is fully buffered and flushed when the process
|
||||||
|
# ends, so a prompt that appears before input is read and a prompt that appears an hour
|
||||||
|
# later produce byte-identical files. A prompt printed after the answer it was asking for
|
||||||
|
# is invisible to every other test here.
|
||||||
|
#
|
||||||
|
# WHAT HAPPENS TO THE TERMINAL. Key mode only touches a terminal when there is one, so
|
||||||
|
# with input from a file there is nothing to put into another state and nothing to put
|
||||||
|
# back. A machine that leaves the terminal without echo passes all 92 other tests.
|
||||||
|
#
|
||||||
|
# Both of those have gone wrong in this repository, and both were found by a person whose
|
||||||
|
# terminal stopped working rather than by anything here. So this runs the emulator under a
|
||||||
|
# pseudo-terminal, which is what makes those questions askable at all.
|
||||||
|
#
|
||||||
|
# Written by Anachronaut
|
||||||
|
|
||||||
|
set -u
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
cd "$ROOT" || exit 1
|
||||||
|
|
||||||
|
BUILD="$ROOT/Tests/build/terminal"
|
||||||
|
mkdir -p "$BUILD"
|
||||||
|
|
||||||
|
[ -x "$ROOT/SplitBit" ] || { echo "The emulator is not built."; exit 1; }
|
||||||
|
[ -x "$ROOT/Assembler" ] || { echo "The assembler is not built."; exit 1; }
|
||||||
|
|
||||||
|
# A program that asks for key mode and then waits for a key that never comes. Every check
|
||||||
|
# below needs a machine that is sitting in key mode with the terminal in its hands.
|
||||||
|
cat > "$BUILD/keywait.asm" <<'ASM'
|
||||||
|
#Program
|
||||||
|
start:
|
||||||
|
INIA 0x01
|
||||||
|
OUTA 0x02
|
||||||
|
INA 0x00
|
||||||
|
HALT
|
||||||
|
ASM
|
||||||
|
|
||||||
|
# A program that prints something with no newline after it and then waits, which is the
|
||||||
|
# shape of a prompt and the shape the buffering problem hides in.
|
||||||
|
cat > "$BUILD/prompt.asm" <<'ASM'
|
||||||
|
#Program
|
||||||
|
start:
|
||||||
|
INIA 0d62 ; '>'
|
||||||
|
OUTA 0x00
|
||||||
|
INIA 0x20
|
||||||
|
OUTA 0x00
|
||||||
|
INA 0x00
|
||||||
|
HALT
|
||||||
|
ASM
|
||||||
|
|
||||||
|
"$ROOT/Assembler" "$BUILD/keywait.asm" -o "$BUILD/keywait.bin" >/dev/null 2>&1 || {
|
||||||
|
echo "Could not assemble the terminal test programs."; exit 1; }
|
||||||
|
"$ROOT/Assembler" "$BUILD/prompt.asm" -o "$BUILD/prompt.bin" >/dev/null 2>&1 || {
|
||||||
|
echo "Could not assemble the terminal test programs."; exit 1; }
|
||||||
|
|
||||||
|
python3 - "$ROOT" "$BUILD" <<'PY'
|
||||||
|
import os
|
||||||
|
import pty
|
||||||
|
import select
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
root, build = sys.argv[1], sys.argv[2]
|
||||||
|
emulator = os.path.join(root, "SplitBit")
|
||||||
|
|
||||||
|
passed = 0
|
||||||
|
problems = []
|
||||||
|
|
||||||
|
|
||||||
|
def report(ok, what, detail=""):
|
||||||
|
global passed
|
||||||
|
if ok:
|
||||||
|
passed += 1
|
||||||
|
print(" [ok ] %s" % what)
|
||||||
|
else:
|
||||||
|
problems.append(what if not detail else "%s: %s" % (what, detail))
|
||||||
|
print(" [FAIL] %s%s" % (what, (" - " + detail) if detail else ""))
|
||||||
|
|
||||||
|
|
||||||
|
def underPty(script, seconds=10):
|
||||||
|
"""Runs a shell script with a pseudo-terminal for its controlling terminal, and
|
||||||
|
gives back everything the terminal saw."""
|
||||||
|
pid, fd = pty.fork()
|
||||||
|
if pid == 0:
|
||||||
|
os.execv("/bin/bash", ["/bin/bash", "-c", script])
|
||||||
|
seen = b""
|
||||||
|
end = time.time() + seconds
|
||||||
|
while time.time() < end:
|
||||||
|
ready, _, _ = select.select([fd], [], [], 0.2)
|
||||||
|
if ready:
|
||||||
|
try:
|
||||||
|
chunk = os.read(fd, 4096)
|
||||||
|
except OSError:
|
||||||
|
break
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
seen += chunk
|
||||||
|
if os.waitpid(pid, os.WNOHANG)[0]:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
os.kill(pid, signal.SIGKILL)
|
||||||
|
except ProcessLookupError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
os.waitpid(pid, 0)
|
||||||
|
except ChildProcessError:
|
||||||
|
pass
|
||||||
|
os.close(fd)
|
||||||
|
return seen
|
||||||
|
|
||||||
|
|
||||||
|
# ---- A prompt is shown before the answer to it is asked for ----
|
||||||
|
#
|
||||||
|
# The machine writes "> " and then waits. Nothing more will ever be printed, so if the two
|
||||||
|
# characters have not arrived after a second of waiting, they are sitting in a buffer and
|
||||||
|
# the person at the terminal is looking at nothing and being asked to answer it.
|
||||||
|
#
|
||||||
|
# This is exactly the bug that getchar used to hide: reading through stdio flushed the line
|
||||||
|
# buffered streams first, and reading with read() does not.
|
||||||
|
pid, fd = pty.fork()
|
||||||
|
if pid == 0:
|
||||||
|
os.execv(emulator, [emulator, "--fast", os.path.join(build, "prompt.bin")])
|
||||||
|
seen = b""
|
||||||
|
end = time.time() + 1.5
|
||||||
|
while time.time() < end:
|
||||||
|
ready, _, _ = select.select([fd], [], [], 0.2)
|
||||||
|
if ready:
|
||||||
|
try:
|
||||||
|
seen += os.read(fd, 1024)
|
||||||
|
except OSError:
|
||||||
|
break
|
||||||
|
report(b">" in seen, "a prompt is shown before its answer is read",
|
||||||
|
"" if b">" in seen else "nothing arrived in 1.5s, so it is stuck in a buffer")
|
||||||
|
try:
|
||||||
|
os.kill(pid, signal.SIGKILL)
|
||||||
|
os.waitpid(pid, 0)
|
||||||
|
except (ProcessLookupError, ChildProcessError):
|
||||||
|
pass
|
||||||
|
os.close(fd)
|
||||||
|
|
||||||
|
|
||||||
|
# ---- A key arrives without Return ----
|
||||||
|
#
|
||||||
|
# Which is the whole of what key mode is for. In line mode the terminal holds what is typed
|
||||||
|
# until Return, so a machine that failed to ask for key mode would wait here for ever.
|
||||||
|
pid, fd = pty.fork()
|
||||||
|
if pid == 0:
|
||||||
|
os.execv(emulator, [emulator, "--fast", os.path.join(build, "keywait.bin")])
|
||||||
|
time.sleep(0.5)
|
||||||
|
os.write(fd, b"x") # No newline, on purpose.
|
||||||
|
finished = False
|
||||||
|
end = time.time() + 2
|
||||||
|
while time.time() < end:
|
||||||
|
if os.waitpid(pid, os.WNOHANG)[0]:
|
||||||
|
finished = True
|
||||||
|
break
|
||||||
|
time.sleep(0.05)
|
||||||
|
report(finished, "a single keystroke arrives without Return",
|
||||||
|
"" if finished else "the machine was still waiting, so the key was held by the terminal")
|
||||||
|
try:
|
||||||
|
os.kill(pid, signal.SIGKILL)
|
||||||
|
os.waitpid(pid, 0)
|
||||||
|
except (ProcessLookupError, ChildProcessError):
|
||||||
|
pass
|
||||||
|
os.close(fd)
|
||||||
|
|
||||||
|
|
||||||
|
# ---- The terminal is handed back however the machine dies ----
|
||||||
|
#
|
||||||
|
# atexit covers stopping on purpose and nothing else: it does not run when a process is
|
||||||
|
# killed by a signal. SIGHUP is the one that matters most, because it is what arrives when
|
||||||
|
# whatever launched the machine dies and takes the terminal with it - and a terminal left
|
||||||
|
# in key mode has no echo and no line editing, which is a far worse failure than anything
|
||||||
|
# the program was doing.
|
||||||
|
for name in ("HUP", "INT", "QUIT", "ABRT", "SEGV", "TERM"):
|
||||||
|
# Cleared first. Without this a run that dies before it can measure leaves the PREVIOUS
|
||||||
|
# signal's files in place, and the check compares those and passes - which is how the
|
||||||
|
# SIGQUIT case came to be reporting success while proving nothing at all.
|
||||||
|
for leftover in ("before.txt", "after.txt"):
|
||||||
|
try:
|
||||||
|
os.remove(os.path.join(build, leftover))
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
script = (
|
||||||
|
# No core files. Two of these signals dump core by default, and a test suite has no
|
||||||
|
# business leaving those around every time it runs. It also keeps the measuring
|
||||||
|
# shell alive through SIGQUIT, which otherwise takes it down before it can look.
|
||||||
|
"ulimit -c 0\n"
|
||||||
|
"stty -g > {b}/before.txt\n"
|
||||||
|
"{e} --fast {b}/keywait.bin < /dev/tty &\n"
|
||||||
|
"P=$!\n"
|
||||||
|
"sleep 0.5\n"
|
||||||
|
"kill -{s} $P 2>/dev/null\n"
|
||||||
|
"wait $P 2>/dev/null\n"
|
||||||
|
"sleep 0.3\n"
|
||||||
|
"stty -g > {b}/after.txt\n"
|
||||||
|
).format(b=build, e=emulator, s=name)
|
||||||
|
# < /dev/tty is load bearing. A background job in a non-interactive shell gets its
|
||||||
|
# standard input from /dev/null, so without it the machine never sees a terminal, never
|
||||||
|
# enters key mode, and has nothing to fail to put back - and this check would pass
|
||||||
|
# against a machine that restores nothing at all.
|
||||||
|
underPty(script)
|
||||||
|
try:
|
||||||
|
before = open(os.path.join(build, "before.txt")).read().strip()
|
||||||
|
after = open(os.path.join(build, "after.txt")).read().strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
# A failure, and for SIGQUIT this is the shape the failure takes: a machine that
|
||||||
|
# does not handle it dies in a way that takes the measuring shell with it, so what
|
||||||
|
# is reported is not "the terminal was left wrong" but "nothing got as far as
|
||||||
|
# looking". Both mean the same thing here, which is that the signal is unhandled.
|
||||||
|
report(False, "the terminal is restored after SIG%s" % name, "could not measure")
|
||||||
|
continue
|
||||||
|
report(before == after, "the terminal is restored after SIG%s" % name,
|
||||||
|
"" if before == after else "left as %s, was %s" % (after[:24], before[:24]))
|
||||||
|
|
||||||
|
|
||||||
|
# ---- Suspending is not dying ----
|
||||||
|
#
|
||||||
|
# Ctrl-Z has to hand the terminal back while the machine is stopped, because whoever gets it
|
||||||
|
# next is entitled to find it as they left it, and take key mode again on resume, because
|
||||||
|
# the machine has not finished with it.
|
||||||
|
script = (
|
||||||
|
"ulimit -c 0\n"
|
||||||
|
"stty -g > {b}/t0.txt\n"
|
||||||
|
"{e} --fast {b}/keywait.bin < /dev/tty &\n"
|
||||||
|
"P=$!\n"
|
||||||
|
"sleep 0.5\n"
|
||||||
|
"kill -TSTP $P; sleep 0.4\n"
|
||||||
|
"stty -g > {b}/t1.txt\n"
|
||||||
|
"kill -CONT $P; sleep 0.4\n"
|
||||||
|
"stty -g > {b}/t2.txt\n"
|
||||||
|
"kill -TERM $P; sleep 0.2\n"
|
||||||
|
).format(b=build, e=emulator)
|
||||||
|
underPty(script)
|
||||||
|
try:
|
||||||
|
t0 = open(os.path.join(build, "t0.txt")).read().strip()
|
||||||
|
t1 = open(os.path.join(build, "t1.txt")).read().strip()
|
||||||
|
t2 = open(os.path.join(build, "t2.txt")).read().strip()
|
||||||
|
report(t1 == t0, "the terminal is handed back while suspended")
|
||||||
|
report(t2 != t0, "key mode is taken again on resume")
|
||||||
|
except FileNotFoundError:
|
||||||
|
report(False, "suspending and resuming", "could not measure")
|
||||||
|
|
||||||
|
print()
|
||||||
|
if problems:
|
||||||
|
print("The terminal does not survive everything it should:")
|
||||||
|
for p in problems:
|
||||||
|
print(" " + p)
|
||||||
|
sys.exit(1)
|
||||||
|
print("All %d terminal checks passed." % passed)
|
||||||
|
PY
|
||||||
@@ -76,6 +76,8 @@ test: $(EMU_TARGET) $(ASM_TARGET) $(DSK_TARGET)
|
|||||||
@echo
|
@echo
|
||||||
@./Tests/disk.sh
|
@./Tests/disk.sh
|
||||||
@echo
|
@echo
|
||||||
|
@./Tests/terminal.sh
|
||||||
|
@echo
|
||||||
@./Tests/docs.sh
|
@./Tests/docs.sh
|
||||||
|
|
||||||
# Rebuild both tools with the address and undefined behaviour sanitizers and run
|
# Rebuild both tools with the address and undefined behaviour sanitizers and run
|
||||||
@@ -96,6 +98,8 @@ sanitize:
|
|||||||
@$(MAKE) --no-print-directory CFLAGS="$(SANITIZE_FLAGS)"
|
@$(MAKE) --no-print-directory CFLAGS="$(SANITIZE_FLAGS)"
|
||||||
@echo "Running the test suite under AddressSanitizer and UndefinedBehaviorSanitizer."
|
@echo "Running the test suite under AddressSanitizer and UndefinedBehaviorSanitizer."
|
||||||
@./Tests/run.sh
|
@./Tests/run.sh
|
||||||
|
@echo
|
||||||
|
@./Tests/terminal.sh
|
||||||
@$(MAKE) --no-print-directory clean
|
@$(MAKE) --no-print-directory clean
|
||||||
@$(MAKE) --no-print-directory
|
@$(MAKE) --no-print-directory
|
||||||
@echo "Sanitizer run finished cleanly. Normal binaries rebuilt."
|
@echo "Sanitizer run finished cleanly. Normal binaries rebuilt."
|
||||||
|
|||||||
Reference in New Issue
Block a user