CosmOS pre-alpha and launchable application versions of old programs.

This commit is contained in:
Anachronaut
2026-08-17 15:31:49 -04:00
parent eff6902bcf
commit 91c9d49d1b
66 changed files with 5612 additions and 160 deletions
+388
View File
@@ -0,0 +1,388 @@
; console.asm
; Talking to the console.
;
; This is the modern replacement for print.asm, which was written for a machine with one
; Data Pointer and no vector table. The old one is left where it is, because the programs
; that include it still work and are meant to keep working.
;
; Two things are different here, and both are deliberate.
;
; There is no branch at the top. print.asm begins with BRI start, so that a program
; including it arrives at its own entry point instead of falling into the library. That
; was the only way to do it before the Boot vector existed. A program including this file
; says where it begins in its own Vector Segment:
;
; #Vectors
; Boot start
;
; And every routine here names the Data Pointer it works through rather than assuming the
; only one. A pointer handed in is DP0. Nothing here disturbs what the caller left in
; DP3, which is the one a return survives in.
;
; What a routine gives back is in Q, because Q and DP3 are the only things a RET does not
; put back the way it found them.
;
; Written by Anachronaut
#Program
; ---- Characters and strings ----
; A line feed.
newLine:
INIA 0x0A
OUTA 0x00
RET
; DP0 names a string ending in a zero byte. Prints it.
printString:
LDA.0
BRA printStringDone
OUTA 0x00
INCD.0
BRI printString
printStringDone:
RET
; A holds how many spaces to print. None is a fair answer, and prints nothing.
printSpaces:
BRA printSpacesDone
INIB 0x20
printSpacesLoop:
OUTB 0x00
DECA
BNA printSpacesLoop
printSpacesDone:
RET
; ---- Hexadecimal ----
; A holds a byte. Prints it as two hexadecimal digits, high one first.
;
; A and B are a circular shift register sixteen bits long, so rotating right four times
; with B empty walks the high nybble down into place and parks the low one in B. The call
; between the two halves puts A and B back as they were, which is what lets the second
; rotation find the low nybble still waiting.
printByteHex:
RSTB
SHR SHR SHR SHR
CALL printHexDigit
RSTA
SHL SHL SHL SHL
CALL printHexDigit
RET
; DP0 names two bytes, most significant first, the way every number on a SplitBit disk is
; stored. Prints them as four hexadecimal digits.
printWordHex:
LDA.0
CALL printByteHex
INCD.0
LDA.0
CALL printByteHex
RET
; A holds a nybble. Prints the one character that stands for it.
printHexDigit:
INIB 0d10
CCF
SUB
BRC printDecimalDigit ; Under ten, so it is a plain digit.
INIB 0x37 ; 'A' is ten, so this is the offset that gets there.
CCF
ADD
OUTQ 0x00
RET
; A holds a digit from zero to nine. Prints it.
printDecimalDigit:
INIB 0x30
CCF
ADD
OUTQ 0x00
RET
; ---- Decimal ----
; A holds a byte. Prints it in decimal, without leading zeroes.
printByteDecimal:
SETD.0 ConsoleValue
STA.0
SETD.1 ConsoleLeading
RSTA
STA.1 ; Nothing has been printed yet.
INIA 0d100
CALL printBytePlace
INIA 0d10
CALL printBytePlace
; Whatever is left is the ones, and it prints whether or not it is a zero, because a
; number has to show at least one digit.
SETD.0 ConsoleValue
LDA.0
CALL printDecimalDigit
RET
; A holds a power of ten. Counts how many times it comes out of ConsoleValue, prints that
; as a digit, and leaves the remainder behind. A leading zero prints nothing.
printBytePlace:
SETD.2 ConsoleBytePower
STA.2
SETD.0 ConsoleValue
SETD.1 ConsoleCount
RSTA
STA.1
printBytePlaceLoop:
LDA.0
LDB.2
CCF
SUB
BRC printBytePlaceDone ; It went below zero, so it does not come out again.
STQ.0
LDA.1
INCA
STA.1
BRI printBytePlaceLoop
printBytePlaceDone:
LDA.1
BNA printBytePlaceShow
; The digit is a zero, which only prints if something has been printed before it.
SETD.2 ConsoleLeading
LDA.2
BRA printBytePlaceQuiet
RSTA
printBytePlaceShow:
CALL printDecimalDigit
SETD.2 ConsoleLeading
INIA 0x01
STA.2
printBytePlaceQuiet:
RET
; DP0 names a two byte number, most significant byte first. Prints it in decimal, without
; leading zeroes. Sixty five thousand five hundred and thirty five is the largest thing it
; can be handed, which is the whole of an address, so nothing overflows this.
printWordDecimal:
SETD.1 ConsoleWord
CALL consoleCopyWord
SETD.1 ConsoleLeading
RSTA
STA.1
SETD.0 ConsoleTenThousand
CALL printWordPlace
SETD.0 ConsoleThousand
CALL printWordPlace
SETD.0 ConsoleHundred
CALL printWordPlace
SETD.0 ConsoleTen
CALL printWordPlace
; What is left is under ten, so it is in the low byte and it is the last digit.
SETD.0 ConsoleWord
INCD.0
LDA.0
CALL printDecimalDigit
RET
; DP0 names a power of ten, two bytes of it. The same counting as printBytePlace, done
; sixteen bits wide.
printWordPlace:
SETD.1 ConsolePower
CALL consoleCopyWord
SETD.1 ConsoleCount
RSTA
STA.1
printWordPlaceLoop:
CALL consoleTakePower
BNQ printWordPlaceDone
SETD.1 ConsoleCount
LDA.1
INCA
STA.1
BRI printWordPlaceLoop
printWordPlaceDone:
SETD.1 ConsoleCount
LDA.1
BNA printWordPlaceShow
SETD.1 ConsoleLeading
LDA.1
BRA printWordPlaceQuiet
RSTA
printWordPlaceShow:
CALL printDecimalDigit
SETD.1 ConsoleLeading
INIA 0x01
STA.1
printWordPlaceQuiet:
RET
; Takes ConsolePower out of ConsoleWord, if it comes out at all. Q is zero if it did, and
; then ConsoleWord is the smaller for it. If it did not, ConsoleWord is left alone.
;
; The subtraction is done into a spare word rather than in place, because whether it fits
; is not known until the high half is done, and by then an in place low half would already
; have been spent.
;
; The low half clears the Carry Flag first and the high half does not: the borrow the low
; half leaves behind is exactly what the high half has to subtract as well. Nothing
; between them disturbs it, since only the arithmetic instructions and CCF touch it.
consoleTakePower:
SETD.0 ConsoleWord
INCD.0
SETD.1 ConsolePower
INCD.1
LDA.0
LDB.1
CCF
SUB
SETD.2 ConsoleSpare
INCD.2
STQ.2
SETD.0 ConsoleWord
SETD.1 ConsolePower
LDA.0
LDB.1
SUB
BRC consoleTakeNothing
SETD.2 ConsoleSpare
STQ.2
SETD.0 ConsoleSpare
SETD.1 ConsoleWord
CALL consoleCopyWord
RSTA
RSTB
CCF
ADD ; Q is zero: it came out.
RET
consoleTakeNothing:
RSTA
INIB 0x01
CCF
ADD ; Q is one: it did not.
RET
; Two bytes from DP0 to DP1, most significant first.
consoleCopyWord:
LDA.0
STA.1
INCD.0
INCD.1
LDA.0
STA.1
RET
; ---- Reading ----
; DP0 names a buffer and B says how many characters it holds, not counting the zero byte
; that ends it. Reads a line from the console into it. Q is how long the line turned out
; to be.
;
; A line longer than the buffer is cut short, and the rest of it is read and thrown away
; rather than left to turn up as the next line.
;
; ConsoleEndOfInput is set if the console ran out instead of ending a line. That is a
; different thing from an empty line, and a program that reads until there is no more has
; to be able to tell them apart.
readLine:
SETD.1 ConsoleRoom
STB.1
SETD.1 ConsoleLength
RSTA
STA.1
SETD.1 ConsoleEndOfInput
STA.1
readLineNext:
INA 0x00
INIB 0x0A
CCF
SUB
BRQ readLineDone ; A line feed ends the line. A is still the character.
INIB 0xFF
CCF
SUB
BRQ readLineEnd ; There is no more to be had.
; Is there room for it? A still holds the character, so it is put somewhere safe while
; the counting is done.
SETD.1 ConsoleChar
STA.1
SETD.1 ConsoleLength
LDA.1
SETD.1 ConsoleRoom
LDB.1
CCF
SUB
BRQ readLineNext ; Full. Read on, and drop what comes.
SETD.1 ConsoleChar
LDA.1
STA.0
INCD.0
SETD.1 ConsoleLength
LDA.1
INCA
STA.1
BRI readLineNext
readLineEnd:
SETD.1 ConsoleEndOfInput
INIA 0x01
STA.1
readLineDone:
RSTA
STA.0 ; The zero byte that ends it.
SETD.1 ConsoleLength
LDA.1
RSTB
CCF
ADD ; Q is how long the line is.
RET
#Data
; ---- What readLine keeps while it works ----
ConsoleRoom:
0x00
ConsoleLength:
0x00
ConsoleChar:
0x00
; Set when the console ran out rather than ending a line. Cleared at the start of every
; readLine, so it always describes the last line read.
ConsoleEndOfInput:
0x00
; ---- What the number routines keep while they work ----
; Whether any digit has been printed yet, which is what decides if a zero is a leading
; one or a real one.
ConsoleLeading:
0x00
ConsoleCount:
0x00
ConsoleValue:
0x00
ConsoleBytePower:
0x00
ConsolePower:
0x00 0x00
ConsoleWord:
0x00 0x00
ConsoleSpare:
0x00 0x00
; The powers of ten, written the way every number here is written: most significant byte
; first.
ConsoleTenThousand:
0x27 0x10
ConsoleThousand:
0x03 0xE8
ConsoleHundred:
0x00 0x64
ConsoleTen:
0x00 0x0A
+781
View File
@@ -0,0 +1,781 @@
; cosmos.asm
; CosmOS, and the shell that is most of it.
;
; The machine boots into this. It registers what the hardware brought, mounts whatever
; disk is attached, and then reads lines and does what they say until there is no more
; typing to be had.
;
; ---- Where things live ----
;
; The system keeps to the bottom of both memories, and everything above is for whatever
; it is running:
;
; Program Memory 0x0000 - 0x1FFF the system
; 0x2000 - a loaded program's code
; Data Memory 0x0000 - 0x0FFF the system
; 0x1000 - a loaded program's data
;
; Nothing enforces that. Nothing can: the fence guards a range, and this is a convention
; about which range belongs to whom rather than a rule about what may be touched. The
; assembler prints both segment sizes, and they are what to watch.
;
; A program is staged at 0x8000 while it is being loaded, which is inside the region a
; loaded program will own. That is safe because nothing is running during a load, and it
; is where a big program can be read without the system reserving the room for good.
;
; ---- What it can do ----
;
; dir List what is on the disk.
; load Read a program off the disk and put it where it asks to go.
; run Start the program that was loaded.
; help Say what these are.
; exit Stop.
;
; dump is next. The dispatch below is a chain of comparisons, which is the right shape for
; five commands and the wrong shape for twenty; when it grows, the table that
; dispatchTest.asm demonstrates is where it should go.
;
; Written by Anachronaut
#Include console.asm
#Include text.asm
#Include sbfs.asm
#Include services.asm
#Program
boot:
SETD.0 Banner
CALL printString
CALL newLine
; Find out whether there is a filesystem to talk to. Doing this once at boot rather than
; once per command means a disk swapped underneath us is not noticed, which is honest
; for a machine whose disk is a file named on the command line.
CALL sbfsMount
SETD.0 DiskReady
BNQ bootNoDisk
INIA 0x01
STA.0
BRI prompt
bootNoDisk:
RSTA
STA.0
SETD.0 NoDisk
CALL printString
CALL newLine
; ---- The loop ----
prompt:
SETD.0 PromptText
CALL printString
SETD.0 CommandLine
INIB 0d63
CALL readLine
; Running out of typing is how this ends. It is not the same as an empty line, which is
; just somebody pressing return, and the shell should sit there when that happens.
SETD.0 ConsoleEndOfInput
LDA.0
BNA quitRanOut
SETD.0 CommandLine
CALL textSplit
; An empty line asks for nothing.
SETD.0 CommandLine
LDA.0
BRA prompt
SETD.0 CommandLine
SETD.1 DirName
CALL textSame
BRQ doDir
SETD.0 CommandLine
SETD.1 LoadName
CALL textSame
BRQ doLoad
SETD.0 CommandLine
SETD.1 RunName
CALL textSame
BRQ doRun
SETD.0 CommandLine
SETD.1 DumpName
CALL textSame
BRQ doDump
SETD.0 CommandLine
SETD.1 HelpName
CALL textSame
BRQ doHelp
SETD.0 CommandLine
SETD.1 ExitName
CALL textSame
BRQ quit
; Nothing matched. Saying which word was not understood is worth the four instructions:
; it tells somebody who mistyped what they actually typed.
SETD.0 Unknown
CALL printString
SETD.0 CommandLine
CALL printString
CALL newLine
BRI prompt
; Running out of console leaves the cursor part way along a line, because there was no
; return at the end to move it on. Somebody who typed "exit" has already pressed one, and
; a second would only leave a blank line behind.
quitRanOut:
CALL newLine
quit:
SETD.0 Farewell
CALL printString
CALL newLine
HALT
; ---- dir ----
;
; Walks the directory and prints what is in it. A free entry in the middle of a directory
; is stepped over by the walk, so what comes out is the files and nothing else.
doDir:
SETD.0 DiskReady
LDA.0
BRA dirNoDisk
RSTA
SETD.0 DirSeen
STA.0
CALL sbfsFirst
BRI dirCheck
dirStep:
CALL sbfsNext
dirCheck:
BNQ dirDone
SETD.0 DirSeen
LDA.0
INCA
STA.0
SETD.0 SbfsName
CALL printString
SETD.0 SbfsName
CALL nameWidth
MVQA
CALL printSpaces
; A file's length is its block count times 256 plus its tail, which is the block count
; in the high byte and the tail in the low one. Nothing has to multiply anything.
SETD.0 SbfsFileBlocks
DPUP.0 0d01
LDA.0
SETD.1 DirSize
STA.1
SETD.0 SbfsFileTail
LDA.0
SETD.1 DirSize
INCD.1
STA.1
SETD.0 DirSize
CALL printWordDecimal
CALL newLine
BRI dirStep
dirDone:
SETD.0 DirSeen
LDA.0
CALL printByteDecimal
SETD.0 FilesText
CALL printString
CALL newLine
BRI prompt
dirNoDisk:
SETD.0 NoDisk
CALL printString
CALL newLine
BRI prompt
; DP0 names a string. Q is how many spaces pad it out to twenty four columns. A name
; already that long gets one space, so that it cannot run into the number after it.
nameWidth:
INIA 0d24
SETD.1 WidthLeft
STA.1
widthLoop:
LDA.0
BRA widthDone
SETD.1 WidthLeft
LDA.1
DECA
STA.1
BRA widthFloor
INCD.0
BRI widthLoop
widthFloor:
INIA 0d1
SETD.1 WidthLeft
STA.1
widthDone:
SETD.1 WidthLeft
LDA.1
RSTB
CCF
ADD
RET
; ---- load ----
;
; Reads a program off the disk and puts it where its header asks to go. Nothing relocates
; anything: the addresses in the header are the ones the program was built for, and it
; would not work anywhere else.
;
; The whole file is staged at 0x8000 first and then blitted into place, because where the
; pieces belong is not known until the header has been read, and the header is in the file.
doLoad:
SETD.0 DiskReady
LDA.0
BRA loadNoDisk
SETD.1 TextRest
LDD.0.1
LDA.0
BRA loadNothingNamed
CALL sbfsFind
BNQ loadMissing
SETD.1 0x80 0x00
CALL sbfsRead
BNQ loadUnreadable
; "SBEX", or this is not a program. Without this, loading a text file would put nonsense
; into Program Memory and then jump into the middle of it.
SETD.0 0x80 0x00
SETD.2 ExecMagic
INIA 0d4
SETD.1 LoadCount
STA.1
loadMagicLoop:
LDA.0
LDB.2
XOR
BNQ loadNotProgram
INCD.0
INCD.2
LDA.1
DECA
STA.1
BNA loadMagicLoop
SETD.0 0x80 0x00
DPUP.0 0d04
LDA.0
INIB 0d1
XOR
BNQ loadWrongVersion
; The code. It comes from the staging area just past the sixteen byte header, and goes
; wherever the header says, in Program Memory, which the instruction set cannot write
; and the controller can.
INIA 0d1
OUTA 0xE0 ; SourceBank: Data Memory, where the file was staged.
INIA 0x80
OUTA 0xE1
INIA 0d16
OUTA 0xE2 ; 0x8010, the first byte after the header.
RSTA
OUTA 0xE3 ; DestBank: Program Memory.
SETD.0 0x80 0x00
DPUP.0 0d06
LDA.0
OUTA 0xE4
INCD.0
LDA.0
OUTA 0xE5
SETD.0 0x80 0x00
DPUP.0 0d10
LDA.0
OUTA 0xE6
INCD.0
LDA.0
OUTA 0xE7
INIA 0x01
OUTA 0xE8 ; Blit.
; Then the data. A blit leaves its addresses past whatever it touched, so the source is
; already sitting on the first byte of the data and only the destination changes.
INIA 0d1
OUTA 0xE3 ; DestBank: Data Memory.
SETD.0 0x80 0x00
DPUP.0 0d12
LDA.0
OUTA 0xE4
INCD.0
LDA.0
OUTA 0xE5
SETD.0 0x80 0x00
DPUP.0 0d14
LDA.0
OUTA 0xE6
INCD.0
LDA.0
OUTA 0xE7
INIA 0x01
OUTA 0xE8 ; Blit.
; Where it starts. Written out by hand rather than through a routine, because a routine
; could not hand two bytes back: CALL puts A, B and the first three pointers back the
; way it found them.
SETD.0 0x80 0x00
DPUP.0 0d08
LDA.0
SETD.1 LoadedEntry
STA.1
INCD.0
INCD.1
LDA.0
STA.1
INIA 0x01
SETD.0 LoadedOk
STA.0
SETD.0 LoadedText
CALL printString
SETD.0 LoadedEntry
CALL printWordHex
CALL newLine
BRI prompt
loadNoDisk:
SETD.0 NoDisk
BRI loadComplain
loadNothingNamed:
SETD.0 LoadWhat
BRI loadComplain
loadMissing:
SETD.0 NoSuchFile
BRI loadComplain
loadUnreadable:
SETD.0 Unreadable
BRI loadComplain
loadNotProgram:
SETD.0 NotProgram
BRI loadComplain
loadWrongVersion:
SETD.0 WrongVersion
loadComplain:
CALL printString
CALL newLine
BRI prompt
; ---- run ----
;
; Hands the machine to whatever was loaded. Where the Stack is now is written down first,
; because the program is not going to unwind anything it pushes and the exit handler has
; to be able to put the Stack back.
doRun:
SETD.0 LoadedOk
LDA.0
BRA runNothing
MVSD.0
SETD.1 SystemStack
STD.0.1
; The entry address is a number until BRD makes it a place. DP3 is the one to build it
; in, because it is the pointer nothing puts back.
SETD.1 LoadedEntry
LDD.3.1
BRD.3
runNothing:
SETD.0 NothingLoaded
CALL printString
CALL newLine
BRI prompt
; ---- The services ----
;
; These are what a loaded program is allowed to ask for. The names and their numbers come
; from services.asm, which the programs include as well, so neither side writes a number
; down and the two cannot disagree about them.
;
; A handler arrives with the caller's registers exactly as they were: an interrupt frame
; is pushed, not cleared. So the pointer a program put in DP0 is still there to be used.
handlePrintString:
CALL printString
RETI
handleReadLine:
CALL readLine
RETI
; Giving the machine back. This is the one place MVDS earns its keep. The program's Stack,
; and the frame this very interrupt arrived on, are both abandoned where they lie, because
; nothing is going to return through either of them.
;
; Which is exactly why this cannot RETI. Its return address is on the Stack it just walked
; away from, so it branches to the prompt instead.
handleExit:
SETD.1 SystemStack
LDD.0.1
MVDS.0
; The console goes back to line mode whatever the program left it in. A program that
; wanted keys is expected to put it back itself, but one that stopped early, or forgot,
; would otherwise hand back a shell with no echo and no backspace, and the shell has no
; way to find out that happened. Writing line mode when it is already in line mode costs
; a byte out of a port and does nothing, which is the right price for not having to know.
RSTA
OUTA 0x02
SETD.0 Finished
CALL printString
CALL newLine
BRI prompt
; ---- dump ----
;
; dump Sixty four more bytes, carrying on from the last one.
; dump <where> From the start of that bank.
; dump <where> <addr> From there.
;
; <where> is program, data, or a bank number in hexadecimal. That the CPU cannot read
; Program Memory and this can is the whole point: the instruction set has no way to look
; at itself, and the controller does, so a monitor is possible at all only through it.
doDump:
SETD.1 TextRest
LDD.0.1
LDA.0
BRA dumpGo ; Nothing said, so carry on from where the last one stopped.
; Which bank. The two that always exist have names, because typing "program" is what
; somebody means and 0 is what the machine calls it.
CALL textSplit
SETD.1 ProgramWord
CALL textSame
BRQ dumpBankProgram
SETD.1 DataWord
CALL textSame
BRQ dumpBankData
CALL textHexWord
BNQ dumpBadWhere
SETD.0 TextValue
INCD.0
LDA.0
BRI dumpSetBank
dumpBankProgram:
RSTA
BRI dumpSetBank
dumpBankData:
INIA 0d1
dumpSetBank:
SETD.0 DumpBank
STA.0
; And where in it. Naming a bank without an address means the start of it, which is the
; only answer that does not depend on what was asked for last time.
RSTA
SETD.0 DumpAt
STA.0
INCD.0
STA.0
SETD.1 TextRest
LDD.0.1
LDA.0
BRA dumpCheckBank
CALL textHexWord
BNQ dumpBadWhere
SETD.0 TextValue
LDA.0
SETD.1 DumpAt
STA.1
SETD.0 TextValue
INCD.0
LDA.0
SETD.1 DumpAt
INCD.1
STA.1
dumpCheckBank:
; Is there such a bank? Asking the controller for a bank that is not there is refused,
; and a refusal nobody catches stops the machine, which is a poor answer to a typing
; mistake. The bank table says what exists, and it lives in bank 2.
;
; Bank n's record starts at n times eight. A and B are a shift register sixteen bits
; wide, so putting the number in the low half and rotating left three times multiplies
; it by eight without anything falling off the top: the most it can reach is 2040.
RSTA
SETD.0 DumpBank
LDB.0
SHL SHL SHL
SETD.0 DumpRecord
STA.0
INCD.0
STB.0
INIA 0d2
OUTA 0xE0 ; SourceBank: the controller's own memory.
SETD.0 DumpRecord
LDA.0
OUTA 0xE1
INCD.0
LDA.0
OUTA 0xE2
INA 0xE9 ; The flags byte of that bank's record.
INIB 0x01
AND
BRQ dumpNoBank ; The present bit is down, so nothing is there.
dumpGo:
INIA 0d4
SETD.0 DumpRows
STA.0
dumpRow:
SETD.0 DumpAt
CALL printWordHex
INIA 0d2
CALL printSpaces
; Point the controller at the row. Reading the Data port takes a byte and steps the
; source on, so the whole row is one instruction repeated.
SETD.0 DumpBank
LDA.0
OUTA 0xE0
SETD.0 DumpAt
LDA.0
OUTA 0xE1
INCD.0
LDA.0
OUTA 0xE2
; Sixteen bytes, kept as they go past so that they can be shown twice.
INIA 0d16
SETD.0 DumpCount
STA.0
SETD.1 DumpBytes
dumpByte:
INA 0xE9
STA.1
CALL printByteHex
INIA 0x20
OUTA 0x00
INCD.1
SETD.0 DumpCount
LDA.0
DECA
STA.0
BNA dumpByte
; The same sixteen again, as characters. Anything that is not printable shows as a dot,
; because a control character sent to the console would move the cursor and ruin the
; shape of the dump.
INIA 0x20
OUTA 0x00
INIA 0d16
SETD.0 DumpCount
STA.0
SETD.1 DumpBytes
dumpChar:
LDA.1
INIB 0x20
CCF
SUB
BRC dumpDot ; Below a space.
INIB 0x7F
CCF
SUB
BNC dumpDot ; Delete, or above it.
OUTA 0x00
BRI dumpCharNext
dumpDot:
INIA 0x2E
OUTA 0x00
dumpCharNext:
INCD.1
SETD.0 DumpCount
LDA.0
DECA
STA.0
BNA dumpChar
CALL newLine
; Sixteen further along, carrying into the high byte if the low one wrapped.
SETD.0 DumpAt
INCD.0
LDA.0
INIB 0d16
CCF
ADD
STQ.0
BNC dumpRowNext
SETD.0 DumpAt
LDA.0
INCA
STA.0
dumpRowNext:
SETD.0 DumpRows
LDA.0
DECA
STA.0
BNA dumpRow
BRI prompt
dumpBadWhere:
SETD.0 DumpUsage
CALL printString
CALL newLine
BRI prompt
dumpNoBank:
SETD.0 NoSuchBank
CALL printString
CALL newLine
BRI prompt
; ---- help ----
doHelp:
SETD.0 HelpText
CALL printString
CALL newLine
SETD.0 HelpMoreText
CALL printString
CALL newLine
BRI prompt
#Data
Banner:
"CosmOS"
PromptText:
"> "
NoDisk:
"no filesystem on the disk"
Unknown:
"I do not know: "
Farewell:
"halted"
FilesText:
" files"
; Two strings rather than one, because a string literal stops at 255 characters and each
; one carries its own zero byte, so they are printed in turn rather than joined.
HelpText:
"dir list what is on the disk
load <file> read a program off the disk
run start what was loaded"
HelpMoreText:
"dump sixty four bytes of memory, and again for more
dump <program|data|bank> <address>
help this
exit stop"
DumpUsage:
"dump <program|data|bank> <address>"
NoSuchBank:
"there is no such bank"
ProgramWord:
"program"
DataWord:
"data"
DumpName:
"dump"
ExecMagic:
"SBEX"
LoadWhat:
"load what?"
NoSuchFile:
"no such file"
Unreadable:
"could not read it"
NotProgram:
"not a program"
WrongVersion:
"a version I do not know"
LoadedText:
"loaded, starting at "
NothingLoaded:
"nothing is loaded"
Finished:
"finished"
DirName:
"dir"
LoadName:
"load"
RunName:
"run"
HelpName:
"help"
ExitName:
"exit"
DiskReady:
0x00
LoadedOk:
0x00
LoadedEntry:
0x00 0x00
LoadCount:
0x00
; Where the monitor is looking, so that a bare 'dump' can carry on from it.
DumpBank:
0x00
DumpAt:
0x00 0x00
DumpRows:
0x00
DumpCount:
0x00
DumpRecord:
0x00 0x00
DumpBytes:
#Reserve 0d16
; Where the system's Stack was when it handed the machine to a program. Kept below the
; region a program owns, so that a program has to go looking to break it.
SystemStack:
0x00 0x00
DirSeen:
0x00
DirSize:
0x00 0x00
WidthLeft:
0x00
; Sixty three characters and the zero byte that ends them.
CommandLine:
#Reserve 0d64
#Vectors
Boot boot
osPrintString handlePrintString
osReadLine handleReadLine
osExit handleExit
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
; The services the system offers, named and numbered.
;
; Both sides include this. The system follows it with handlers for the ones it implements.
; A program that only calls them includes this and nothing else, and can then say them by
; name, because a line with a name and nothing after it declares what a vector is called
; and what number it has without claiming to implement it.
;
; The order here is what fixes the numbers, and it is fixed in one file, so the two sides
; cannot disagree about them and nobody has to write a number down. Adding a service goes
; at the end: putting one in the middle would renumber everything after it, and any
; program already assembled against the old numbers would call the wrong thing.
;
; Written by Anachronaut
#Vectors
osPrintString ; DP0 names a string. Prints it.
osReadLine ; DP0 names somewhere to put a line read from the console.
osExit ; Give the machine back to the system.
+230
View File
@@ -0,0 +1,230 @@
; text.asm
; Picking a line of typing apart.
;
; A shell reads a line and has to decide what was asked for. That is two jobs: cutting the
; first word off the line, and telling whether a word is the one being looked for. There
; is nothing else here, because there is nothing else a command line needs yet.
;
; Written by Anachronaut
#Program
; DP0 names a line ending in a zero byte. Cuts the first word off it, in place, by writing
; a zero byte over the space that ends the word. DP0 is unchanged, because a RET puts it
; back, so afterwards DP0 names just the first word.
;
; Where the rest of the line begins goes in TextRest, with any spaces between skipped. A
; line with only one word on it leaves TextRest naming that line's zero byte, which reads
; as an empty argument rather than as a missing one, and is the same thing here.
textSplit:
LDA.0
BRA textSplitHere ; The line ended, so the whole of it was one word.
INIB 0x20
CCF
SUB
BRQ textSplitCut
INCD.0
BRI textSplit
textSplitCut:
RSTA
STA.0 ; The space becomes the end of the word.
INCD.0
textSplitSkip:
LDA.0
BRA textSplitHere
INIB 0x20
CCF
SUB
BNQ textSplitHere ; Something that is not a space: the rest starts here.
INCD.0
BRI textSplitSkip
textSplitHere:
SETD.1 TextRest
STD.0.1
RET
; DP0 and DP1 name strings ending in zero bytes. Q is zero if they are the same.
;
; The two ending together is what makes them the same. Comparing until one of them ends
; would call "dir" and "dirty" the same word, which is the kind of thing a shell gets
; wrong once and confusingly.
textSame:
LDA.0
LDB.1
CCF
SUB
BNQ textDiffer
LDA.0
BRA textAlike ; Equal, and both of them zero: they ended together.
INCD.0
INCD.1
BRI textSame
textAlike:
RSTA
RSTB
CCF
ADD ; Q is zero: the same.
RET
textDiffer:
RSTA
INIB 0d1
CCF
ADD ; Q is one: not the same.
RET
; DP0 names text. Reads hexadecimal digits off the front of it into TextValue, most
; significant byte first. Q is zero if there was at least one digit to read.
;
; Digits past the fourth push the earlier ones off the top rather than being refused,
; which is what typing over an address does on every monitor there has ever been.
textHexWord:
RSTA
SETD.1 TextValue
STA.1
INCD.1
STA.1
SETD.1 TextDigits
STA.1
textHexLoop:
LDA.0
CALL textHexDigit
PSHQ
POPA
INIB 0xFF
CCF
SUB
BRQ textHexEnd ; Not a digit, so the number stopped before it.
CALL textHexShift
SETD.1 TextDigits
LDA.1
INCA
STA.1
INCD.0
BRI textHexLoop
textHexEnd:
SETD.1 TextDigits
LDA.1
BRA textHexNothing
RSTA
RSTB
CCF
ADD ; Q is zero: there was a number.
RET
textHexNothing:
RSTA
INIB 0d1
CCF
ADD ; Q is one: there was not.
RET
; A holds the digit just read. Moves TextValue up by one place and puts the digit in the
; hole that leaves.
;
; A and B are a circular shift register sixteen bits long, so rotating them left four
; times multiplies the pair by sixteen. What fell off the top of the high byte comes round
; into the bottom of the low one, which is exactly the nybble the new digit wants, so it
; is masked away first.
textHexShift:
SETD.1 TextDigit
STA.1
SETD.0 TextValue
LDA.0
INCD.0
LDB.0
SHL SHL SHL SHL
SETD.0 TextValue
STA.0 ; The high byte is finished.
PSHB
POPA
INIB 0xF0
AND
MVQA
SETD.1 TextDigit
LDB.1
OR
SETD.0 TextValue
INCD.0
STQ.0
RET
; A holds a character. Q is what it is worth as a hexadecimal digit, or 0xFF if it is not
; one. Upper and lower case both count, because nobody wants to be told which they meant.
;
; Everything below works from the distance above '0', which is why the letters are tested
; at seventeen and thirty two rather than at anything recognisable.
textHexDigit:
INIB 0x30
CCF
SUB
BRC textHexNo ; Below '0'.
MVQA
INIB 0d10
CCF
SUB
BNC textHexUpper ; Ten or more above '0', so not 0 to 9.
RSTB
CCF
ADD ; Q is the digit itself.
RET
textHexUpper:
INIB 0d17
CCF
SUB
BRC textHexNo ; Between '9' and 'A'.
MVQA
INIB 0d6
CCF
SUB
BNC textHexLower ; Past 'F'.
INIB 0d10
CCF
ADD
RET
textHexLower:
INIB 0d32
CCF
SUB
BRC textHexNo ; Between 'F' and 'a'.
MVQA
INIB 0d6
CCF
SUB
BNC textHexNo ; Past 'f'.
INIB 0d10
CCF
ADD
RET
textHexNo:
RSTA
INIB 0xFF
CCF
ADD
RET
#Data
; Where the rest of the line begins, after textSplit has taken a word off the front.
TextRest:
0x00 0x00
; What textHexWord read, and what it needs while reading it.
TextValue:
0x00 0x00
TextDigits:
0x00
TextDigit:
0x00