Let the shell run a file of commands
'do <file>' runs the lines in a file as though they had been typed. The
only thing a script changes is where the next line comes from: everything
below shellReadLine - splitting the line, matching it, loading a program -
cannot tell the difference and does not have to.
What makes a file a script is '#!' on the front of it, not its name and not
a flag in its entry. The rule this filesystem keeps is that an entry holds
only what the content cannot say about itself, and a script can say what it
is; the loader already refuses anything that is not SBEX, so the two kinds
of runnable file turn each other away without either knowing about the
other. It is also the deferred half of the file-typing design, which said
to wait for a second kind of runnable thing before building any of it. This
is that second kind.
'#' is a directive and ';' is a comment, as in SplitBit assembly - one rule
across the machine rather than two dialects. Not Unix's convention: there
'#!' really is a comment that only the kernel reads, while here the shell
requires it and refuses the file without it, so calling it a comment would
be a lie about what it does.
A script stops at the first line that does not work, which is what the
LineFailed groundwork was for. Comments and blank lines are dropped by the
reader rather than by the dispatch, so they are not echoed either. A script
running out hands back to the console rather than ending the shell, because
running out of file and running out of typing are not the same thing. The
interactive assembler reads through the same path, so a script can contain
a block of assembly.
Three things this cost that were not obvious:
- RET puts A and B back, so a routine cannot answer in them. scriptByte
returning the character in A assembled, ran, and handed the caller its
own A back every time. It answers in memory now.
- A last line with no newline is still a line. Text files do not reliably
end with one and an editor eating it is a bad way to find out a command
did not run.
- Not LastStatus. See the commit before this one.
Six checks in three tests, two of which are about byte positions rather
than behaviour - a command lying across the boundary between two blocks,
and that missing newline - so their fixtures are generated rather than
committed, where an editor cannot helpfully repair them.
Nesting is not in yet: a script cannot run a script. That wants a stack of
positions rather than the one the reader keeps.
Also derives native.sh's self-hosting source list from cosmos.asm's own
#Include lines. It was a hand written list and went stale the moment
script.asm existed - the fourth time a list beside a thing has drifted from
the thing - so it now asks the thing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
fc56e815fc
commit
2466d79d9c
@@ -116,6 +116,56 @@ an installed library and a copy of the source.
|
|||||||
The disk is rebuilt from scratch when its applications change, so its contents describe
|
The disk is rebuilt from scratch when its applications change, so its contents describe
|
||||||
the current source tree rather than accumulating files left by older builds.
|
the current source tree rather than accumulating files left by older builds.
|
||||||
|
|
||||||
|
## Scripts:
|
||||||
|
|
||||||
|
`do <file>` runs the lines in a file as though somebody had typed them. Every command works
|
||||||
|
the same way it does at the prompt, because the only thing a script changes is where the next
|
||||||
|
line comes from - the shell splits it, matches it and runs it without knowing the difference.
|
||||||
|
|
||||||
|
```
|
||||||
|
#! script
|
||||||
|
; Build the system and put it where the machine will find it.
|
||||||
|
cd /Source/CosmOS
|
||||||
|
Asm cosmos.asm
|
||||||
|
```
|
||||||
|
|
||||||
|
**The first two bytes must be `#!`**, or the shell refuses the file and says so. That is what
|
||||||
|
tells a script from anything else, and it is deliberately not the name and not a flag in the
|
||||||
|
directory entry: the rule this filesystem keeps is that an entry holds only what the content
|
||||||
|
cannot say about itself, and a script can say what it is. The loader already refuses anything
|
||||||
|
that does not begin `SBEX`, so the two kinds of runnable file turn each other away without
|
||||||
|
either of them having been told about the other.
|
||||||
|
|
||||||
|
What follows the `#!` is ignored. It is where the name of an interpreter goes if there is ever
|
||||||
|
a second one; today there is one and it is this shell.
|
||||||
|
|
||||||
|
**`#` is a directive and `;` is a comment, exactly as in SplitBit assembly.** One rule across
|
||||||
|
the machine rather than two dialects: `#` means this line is about the file, `;` means ignore
|
||||||
|
this line. Comments and blank lines never reach the shell at all - they are dropped by the
|
||||||
|
reader, so they are not echoed and the dispatch never sees a line it would have to know to
|
||||||
|
ignore. This is not Unix's convention and is not trying to be; there `#!` genuinely is a
|
||||||
|
comment that only the kernel looks at, while here the shell requires it.
|
||||||
|
|
||||||
|
**Each line is echoed as it runs**, after the prompt, so that a script reads exactly like
|
||||||
|
somebody typing it and a script that stops says where.
|
||||||
|
|
||||||
|
**A script stops at the first line that does not work.** A build whose first step failed and
|
||||||
|
whose second step ran anyway produces something wrong and reports success, which is the whole
|
||||||
|
reason the shell now remembers whether a line worked. What counts as not working is a command
|
||||||
|
that failed, a name the shell does not know, or a program that exited with a status. Nothing
|
||||||
|
is printed but `stopped: that line did not work` - whatever failed has already said what was
|
||||||
|
wrong in words.
|
||||||
|
|
||||||
|
**A script running out is not the same as typing running out.** The console ending means
|
||||||
|
there is nobody there and the shell stops; a script ending means go back to whoever asked for
|
||||||
|
it, so the next line comes from the console again.
|
||||||
|
|
||||||
|
The interactive assembler reads its lines the same way, so a script can contain a block of
|
||||||
|
assembly and end it with a `.` just as you would by hand.
|
||||||
|
|
||||||
|
**A script cannot yet run another script.** That is the next thing, and it wants a stack of
|
||||||
|
positions rather than the single one the reader keeps today.
|
||||||
|
|
||||||
## Shell Commands:
|
## Shell Commands:
|
||||||
|
|
||||||
CosmOS currently provides these built-in commands:
|
CosmOS currently provides these built-in commands:
|
||||||
@@ -125,6 +175,7 @@ CosmOS currently provides these built-in commands:
|
|||||||
| `dir` | List the files on the mounted disk and their sizes. |
|
| `dir` | List the files on the mounted disk and their sizes. |
|
||||||
| `load <path>` | Read and validate an SBEX application, then place its code and data where its header requests. |
|
| `load <path>` | Read and validate an SBEX application, then place its code and data where its header requests. |
|
||||||
| `run [words]` | Start the loaded application and make the rest of the line available to it as an argument. |
|
| `run [words]` | Start the loaded application and make the rest of the line available to it as an argument. |
|
||||||
|
| `do <script>` | Run the lines in a file as though they had been typed. See Scripts. |
|
||||||
| `cd [path]` | Go to a directory, or to the root with nothing after it. |
|
| `cd [path]` | Go to a directory, or to the root with nothing after it. |
|
||||||
| `mkdir <path>` | Make a directory. |
|
| `mkdir <path>` | Make a directory. |
|
||||||
| `rmdir <path>` | Remove one, if it is empty. |
|
| `rmdir <path>` | Remove one, if it is empty. |
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
#Include text.asm
|
#Include text.asm
|
||||||
#Include sbfs.asm
|
#Include sbfs.asm
|
||||||
#Include services.asm
|
#Include services.asm
|
||||||
|
#Include script.asm
|
||||||
|
|
||||||
#Program
|
#Program
|
||||||
|
|
||||||
@@ -133,6 +134,24 @@ bootNoDisk:
|
|||||||
; shell. Only saying so leaves the monitor, or a program breaking the machine badly enough
|
; shell. Only saying so leaves the monitor, or a program breaking the machine badly enough
|
||||||
; to need starting again.
|
; to need starting again.
|
||||||
prompt:
|
prompt:
|
||||||
|
; ---- A script stops at the first line that did not work ----
|
||||||
|
;
|
||||||
|
; Checked here, before the next line is read, because this is the one place every command
|
||||||
|
; comes back to. A build whose first step failed and whose second step ran anyway produces
|
||||||
|
; something wrong and says it succeeded, which is the failure this whole flag exists to
|
||||||
|
; prevent.
|
||||||
|
SETD.1 ScriptRunning
|
||||||
|
LDA.1
|
||||||
|
BRA promptWhere
|
||||||
|
SETD.1 LineFailed
|
||||||
|
LDA.1
|
||||||
|
BRA promptWhere
|
||||||
|
SETD.0 ScriptStopped
|
||||||
|
CALL printString
|
||||||
|
CALL newLine
|
||||||
|
CALL scriptClose
|
||||||
|
|
||||||
|
promptWhere:
|
||||||
; Where you are, but only when that is not obvious. At the root the prompt is the one it
|
; Where you are, but only when that is not obvious. At the root the prompt is the one it
|
||||||
; has always been, so a machine nobody has moved about on looks exactly as it did - and
|
; has always been, so a machine nobody has moved about on looks exactly as it did - and
|
||||||
; every recorded test that never says "cd" keeps its recorded prompt.
|
; every recorded test that never says "cd" keeps its recorded prompt.
|
||||||
@@ -160,7 +179,7 @@ promptSay:
|
|||||||
|
|
||||||
SETD.0 CommandLine
|
SETD.0 CommandLine
|
||||||
INIB 0d63
|
INIB 0d63
|
||||||
CALL readLine
|
CALL shellReadLine
|
||||||
|
|
||||||
; Running out of typing is how this ends. It is not the same as an empty line, which is
|
; 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.
|
; just somebody pressing return, and the shell should sit there when that happens.
|
||||||
@@ -230,6 +249,11 @@ promptSay:
|
|||||||
CALL textSame
|
CALL textSame
|
||||||
BRQ doRename
|
BRQ doRename
|
||||||
|
|
||||||
|
SETD.0 CommandLine
|
||||||
|
SETD.1 DoName
|
||||||
|
CALL textSame
|
||||||
|
BRQ doScript
|
||||||
|
|
||||||
SETD.0 CommandLine
|
SETD.0 CommandLine
|
||||||
SETD.1 HelpName
|
SETD.1 HelpName
|
||||||
CALL textSame
|
CALL textSame
|
||||||
@@ -282,6 +306,31 @@ promptSay:
|
|||||||
CALL textSame
|
CALL textSame
|
||||||
BRQ doAssemble
|
BRQ doAssemble
|
||||||
|
|
||||||
|
; ---- Where a line comes from ----
|
||||||
|
;
|
||||||
|
; The whole of what a script is. Everything below this - splitting the line, matching it
|
||||||
|
; against the commands, loading a program - cannot tell the difference and does not have to.
|
||||||
|
;
|
||||||
|
; A SCRIPT RUNNING OUT IS NOT THE SAME AS TYPING RUNNING OUT. The console ending means there
|
||||||
|
; is nobody there and the shell should stop; a script ending means go back to whoever asked
|
||||||
|
; for it. So the end of a script falls through to the console rather than to the door.
|
||||||
|
shellReadLine:
|
||||||
|
SETD.1 ScriptRunning
|
||||||
|
LDA.1
|
||||||
|
BRA shellReadTyped
|
||||||
|
CALL scriptLine
|
||||||
|
BNQ shellReadTyped
|
||||||
|
|
||||||
|
; Echoed, so that a script working can be watched and a script failing says where. It is
|
||||||
|
; printed after the prompt, so it reads exactly like somebody typing it.
|
||||||
|
CALL printString
|
||||||
|
CALL newLine
|
||||||
|
RET
|
||||||
|
|
||||||
|
shellReadTyped:
|
||||||
|
CALL readLine
|
||||||
|
RET
|
||||||
|
|
||||||
; ---- A command that did not work ----
|
; ---- A command that did not work ----
|
||||||
;
|
;
|
||||||
; The one place a failure is recorded, so that the thing reading lines out of a file can
|
; The one place a failure is recorded, so that the thing reading lines out of a file can
|
||||||
@@ -1531,6 +1580,37 @@ fileComplain:
|
|||||||
CALL newLine
|
CALL newLine
|
||||||
BRI commandFailed
|
BRI commandFailed
|
||||||
|
|
||||||
|
; ---- do ----
|
||||||
|
;
|
||||||
|
; Runs the lines in a file as though they had been typed. What makes a file one of these is
|
||||||
|
; the #! on the front of it, not its name and not a flag in its directory entry: the rule is
|
||||||
|
; that the entry holds only what the content cannot say about itself, and a script can say
|
||||||
|
; what it is. The loader already refuses anything that is not SBEX, so the two kinds of
|
||||||
|
; runnable thing turn each other away without either of them knowing about the other.
|
||||||
|
doScript:
|
||||||
|
SETD.1 TextRest
|
||||||
|
LDD.0.1
|
||||||
|
LDA.0
|
||||||
|
BRA scriptNoName
|
||||||
|
|
||||||
|
CALL scriptOpen
|
||||||
|
BRQ prompt ; It is open, and the next line read will come from it.
|
||||||
|
|
||||||
|
; Which of the two went wrong. A number would be no use to anybody here.
|
||||||
|
MVQA
|
||||||
|
INIB 0x01
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BRQ scriptNoFile
|
||||||
|
SETD.0 ScriptNotOne
|
||||||
|
BRI fileComplain
|
||||||
|
scriptNoFile:
|
||||||
|
SETD.0 ScriptNoFile
|
||||||
|
BRI fileComplain
|
||||||
|
scriptNoName:
|
||||||
|
SETD.0 ScriptUsage
|
||||||
|
BRI fileComplain
|
||||||
|
|
||||||
; ---- 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,
|
||||||
@@ -3094,7 +3174,7 @@ assembleLine:
|
|||||||
|
|
||||||
SETD.0 AsmLine
|
SETD.0 AsmLine
|
||||||
INIB 0d40
|
INIB 0d40
|
||||||
CALL readLine
|
CALL shellReadLine
|
||||||
|
|
||||||
INA 0x01
|
INA 0x01
|
||||||
INIB 0x02 ; ENDED, so there is nothing more to assemble.
|
INIB 0x02 ; ENDED, so there is nothing more to assemble.
|
||||||
@@ -3517,6 +3597,14 @@ OnFallback:
|
|||||||
"
|
"
|
||||||
NoDisk:
|
NoDisk:
|
||||||
"no filesystem on the disk"
|
"no filesystem on the disk"
|
||||||
|
ScriptUsage:
|
||||||
|
"do: give me the name of a script"
|
||||||
|
ScriptNoFile:
|
||||||
|
"do: cannot find it"
|
||||||
|
ScriptNotOne:
|
||||||
|
"do: that is not a script - it wants #! on the first line"
|
||||||
|
ScriptStopped:
|
||||||
|
"stopped: that line did not work"
|
||||||
Unknown:
|
Unknown:
|
||||||
"I do not know: "
|
"I do not know: "
|
||||||
Farewell:
|
Farewell:
|
||||||
@@ -3696,6 +3784,8 @@ NeedsValue:
|
|||||||
"that one needs a value after it"
|
"that one needs a value after it"
|
||||||
DirName:
|
DirName:
|
||||||
"dir"
|
"dir"
|
||||||
|
DoName:
|
||||||
|
"do"
|
||||||
LoadName:
|
LoadName:
|
||||||
"load"
|
"load"
|
||||||
RunName:
|
RunName:
|
||||||
|
|||||||
@@ -0,0 +1,352 @@
|
|||||||
|
; Reading shell lines out of a file.
|
||||||
|
;
|
||||||
|
; DP0 = the file's name
|
||||||
|
; CALL scriptOpen Q = 0 and a script is running, or Q says what was wrong:
|
||||||
|
; 1 there is no such file
|
||||||
|
; 2 it is not a script - no #! on the front
|
||||||
|
; DP0 = a buffer, B = how much room
|
||||||
|
; CALL scriptLine Q = 0 and there is a line in the buffer, or nonzero at the end
|
||||||
|
;
|
||||||
|
; Written by Anachronaut
|
||||||
|
;
|
||||||
|
; ---- Why the shell reads files and not the other way round ----
|
||||||
|
;
|
||||||
|
; fileStream.asm does almost this and is deliberately not used. It is application machinery:
|
||||||
|
; More, Type and Wander each include it and each get their own copy in their own Data
|
||||||
|
; Segment. The shell reading a script through it would be a fourth copy, and the shell is the
|
||||||
|
; one place where that copy has to survive running a program - which is exactly the thing
|
||||||
|
; those programs are. Here the state belongs to the system and nothing a program does can
|
||||||
|
; reach it.
|
||||||
|
;
|
||||||
|
; ---- A block, and a nought on the end of it ----
|
||||||
|
;
|
||||||
|
; The reader walks a Data Pointer along the block and stops at a nought. A block is 256 bytes
|
||||||
|
; and a count of them does not fit in a byte, so every other way of knowing where the block
|
||||||
|
; ends costs sixteen bit arithmetic on every character. Writing a nought after the last byte
|
||||||
|
; costs one store per block and turns the whole question into "is this byte zero".
|
||||||
|
;
|
||||||
|
; The buffer is 257 bytes for that reason: a full block leaves the nought at the end of it.
|
||||||
|
; A script cannot contain a nought, which is not a restriction anybody will notice - it is
|
||||||
|
; text, and the #! on the front is what stops a program being read as one.
|
||||||
|
|
||||||
|
#Program
|
||||||
|
|
||||||
|
; ---- Opening ----
|
||||||
|
;
|
||||||
|
; The name is COPIED rather than remembered by address. osFileBlock is given the name again
|
||||||
|
; for every block, and the caller's copy is CommandLine, which the next line typed will
|
||||||
|
; overwrite. fileStream remembers an address and says in its own comment that the address
|
||||||
|
; must stay valid; here it cannot, because the thing that reads the next line is the reason
|
||||||
|
; the name is needed.
|
||||||
|
scriptOpen:
|
||||||
|
SETD.1 ScriptName
|
||||||
|
INIB 0d63
|
||||||
|
CALL copyText
|
||||||
|
|
||||||
|
SETD.0 ScriptName
|
||||||
|
SWI osFileInfo
|
||||||
|
BRQ scriptOpenThere
|
||||||
|
INIA 0x01
|
||||||
|
BRI scriptOpenFailed
|
||||||
|
scriptOpenThere:
|
||||||
|
; DP3 is how many blocks. A file of none is not a script.
|
||||||
|
PSHD.3
|
||||||
|
POPB
|
||||||
|
POPA
|
||||||
|
SETD.1 ScriptBlocks
|
||||||
|
STA.1
|
||||||
|
INCD.1
|
||||||
|
STB.1
|
||||||
|
OR
|
||||||
|
BRQ scriptOpenNotOne
|
||||||
|
|
||||||
|
SETD.1 ScriptIndex
|
||||||
|
RSTA
|
||||||
|
STA.1
|
||||||
|
INCD.1
|
||||||
|
STA.1
|
||||||
|
|
||||||
|
; The first block, so that the #! can be looked at before anything is promised.
|
||||||
|
CALL scriptFill
|
||||||
|
BNQ scriptOpenNotOne
|
||||||
|
|
||||||
|
; ---- What makes a file a script ----
|
||||||
|
;
|
||||||
|
; Two bytes, and the rest of the line ignored. A directive rather than a comment, the way
|
||||||
|
; #Program is in assembly: the shell reads it and refuses the file without it, so calling
|
||||||
|
; it a comment would be a lie about what it does. What follows the #! is where the name of
|
||||||
|
; an interpreter goes when there is a second one; today there is one and it is this shell.
|
||||||
|
SETD.1 ScriptBlock
|
||||||
|
LDA.1
|
||||||
|
INIB 0d35 ; '#'
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BNQ scriptOpenNotOne
|
||||||
|
INCD.1
|
||||||
|
LDA.1
|
||||||
|
INIB 0d33 ; '!'
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BNQ scriptOpenNotOne
|
||||||
|
|
||||||
|
; Past the shebang line, wherever it ends.
|
||||||
|
CALL scriptSkipLine
|
||||||
|
|
||||||
|
INIA 0x01
|
||||||
|
SETD.1 ScriptRunning
|
||||||
|
STA.1
|
||||||
|
RSTA
|
||||||
|
BRI scriptOpenFailed ; A is nought, which is the answer for "it opened".
|
||||||
|
|
||||||
|
scriptOpenNotOne:
|
||||||
|
INIA 0x02
|
||||||
|
scriptOpenFailed:
|
||||||
|
; Q is the answer, and A holds it. Adding nought is how a register becomes Q.
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; ---- One line ----
|
||||||
|
;
|
||||||
|
; Comments and blank lines never reach the shell. The reader drops them, so the echo does not
|
||||||
|
; print them and the dispatch never sees a line it would have to know to ignore.
|
||||||
|
scriptLine:
|
||||||
|
SETD.1 ScriptRoom
|
||||||
|
STB.1
|
||||||
|
SETD.1 ScriptInto
|
||||||
|
STD.0.1
|
||||||
|
|
||||||
|
scriptLineAgain:
|
||||||
|
SETD.1 ScriptLength
|
||||||
|
RSTA
|
||||||
|
STA.1
|
||||||
|
SETD.0 ScriptInto
|
||||||
|
LDD.0.0
|
||||||
|
|
||||||
|
scriptLineNext:
|
||||||
|
CALL scriptByte
|
||||||
|
BNQ scriptLineEnded
|
||||||
|
|
||||||
|
SETD.1 ScriptChar
|
||||||
|
LDA.1
|
||||||
|
INIB 0d10
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BRQ scriptLineDone
|
||||||
|
; A still holds the character: SUB writes Q and leaves it alone.
|
||||||
|
INIB 0d13 ; Carriage return, from a file written on a host that uses them.
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BRQ scriptLineNext
|
||||||
|
|
||||||
|
; Room?
|
||||||
|
SETD.1 ScriptLength
|
||||||
|
LDA.1
|
||||||
|
SETD.1 ScriptRoom
|
||||||
|
LDB.1
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BRQ scriptLineNext ; Full. Read on and drop what comes, the way readLine does.
|
||||||
|
|
||||||
|
SETD.1 ScriptChar
|
||||||
|
LDA.1
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
SETD.1 ScriptLength
|
||||||
|
LDA.1
|
||||||
|
INCA
|
||||||
|
STA.1
|
||||||
|
BRI scriptLineNext
|
||||||
|
|
||||||
|
scriptLineDone:
|
||||||
|
; Terminate it, then decide whether the shell wants to see it.
|
||||||
|
RSTA
|
||||||
|
STA.0
|
||||||
|
|
||||||
|
SETD.0 ScriptInto
|
||||||
|
LDD.0.0
|
||||||
|
LDA.0
|
||||||
|
BRA scriptLineAgain ; Empty.
|
||||||
|
INIB 0d59 ; ';' - a comment, the same as everywhere else on this machine.
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BRQ scriptLineAgain
|
||||||
|
|
||||||
|
RSTA ; Q = 0: there is a line.
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
scriptLineEnded:
|
||||||
|
; ---- A last line with no newline on it is still a line ----
|
||||||
|
;
|
||||||
|
; Text files do not reliably end with one, and a script whose final command silently did
|
||||||
|
; not run because somebody's editor left the newline off is a bad way to find that out.
|
||||||
|
; If anything has been gathered, finish it the ordinary way; the next call comes back here
|
||||||
|
; with nothing gathered and ends for real.
|
||||||
|
SETD.1 ScriptLength
|
||||||
|
LDA.1
|
||||||
|
BRA scriptLineNoMore
|
||||||
|
BRI scriptLineDone
|
||||||
|
|
||||||
|
scriptLineNoMore:
|
||||||
|
CALL scriptClose
|
||||||
|
INIA 0x01
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; ---- One character, or the end ----
|
||||||
|
;
|
||||||
|
; Q = 0 and the character is in ScriptChar, or Q is one and there are no more.
|
||||||
|
;
|
||||||
|
; IN MEMORY RATHER THAN IN A REGISTER, because RET puts A and B back the way the caller had
|
||||||
|
; them - only Q and Data Pointer 3 survive a CALL. Handing the character back in A looked
|
||||||
|
; right, assembled, and returned the caller's own A every time.
|
||||||
|
scriptByte:
|
||||||
|
SETD.1 ScriptAt
|
||||||
|
LDD.1.1
|
||||||
|
LDA.1
|
||||||
|
BRA scriptByteRefill ; The nought at the end of the block.
|
||||||
|
|
||||||
|
; Step the saved pointer past it.
|
||||||
|
SETD.1 ScriptChar
|
||||||
|
STA.1
|
||||||
|
SETD.1 ScriptAt
|
||||||
|
LDD.0.1
|
||||||
|
INCD.0
|
||||||
|
STD.0.1
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD ; Q = 0.
|
||||||
|
RET
|
||||||
|
|
||||||
|
scriptByteRefill:
|
||||||
|
CALL scriptFill
|
||||||
|
BNQ scriptByteNoMore
|
||||||
|
BRI scriptByte
|
||||||
|
scriptByteNoMore:
|
||||||
|
INIA 0x01
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; ---- The next block, with a nought written after it ----
|
||||||
|
;
|
||||||
|
; Q = 0 if there is one.
|
||||||
|
scriptFill:
|
||||||
|
SETD.1 ScriptBlocks
|
||||||
|
LDA.1
|
||||||
|
INCD.1
|
||||||
|
LDB.1
|
||||||
|
OR
|
||||||
|
BRQ scriptFillNoMore
|
||||||
|
|
||||||
|
SETD.0 ScriptName
|
||||||
|
SETD.1 ScriptBlock
|
||||||
|
SETD.2 ScriptIndex
|
||||||
|
LDA.2
|
||||||
|
INCD.2
|
||||||
|
LDB.2
|
||||||
|
SWI osFileBlock
|
||||||
|
BNQ scriptFillNoMore
|
||||||
|
|
||||||
|
; DP3 is how many bytes came back. The nought goes after them.
|
||||||
|
SETD.1 ScriptBlock
|
||||||
|
PSHD.3
|
||||||
|
POPB
|
||||||
|
POPA
|
||||||
|
DPUW.1
|
||||||
|
RSTA
|
||||||
|
STA.1
|
||||||
|
|
||||||
|
SETD.1 ScriptAt
|
||||||
|
SETD.0 ScriptBlock
|
||||||
|
STD.0.1
|
||||||
|
|
||||||
|
; Index++, blocks--.
|
||||||
|
SETD.1 ScriptIndex
|
||||||
|
INCD.1
|
||||||
|
LDA.1
|
||||||
|
INCA
|
||||||
|
STA.1
|
||||||
|
BNC scriptFillCount
|
||||||
|
DECD.1
|
||||||
|
LDA.1
|
||||||
|
INCA
|
||||||
|
STA.1
|
||||||
|
scriptFillCount:
|
||||||
|
SETD.1 ScriptBlocks
|
||||||
|
INCD.1
|
||||||
|
LDA.1
|
||||||
|
BRA scriptFillBorrow
|
||||||
|
DECA
|
||||||
|
STA.1
|
||||||
|
BRI scriptFillGot
|
||||||
|
scriptFillBorrow:
|
||||||
|
INIA 0xFF
|
||||||
|
STA.1
|
||||||
|
DECD.1
|
||||||
|
LDA.1
|
||||||
|
DECA
|
||||||
|
STA.1
|
||||||
|
scriptFillGot:
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
scriptFillNoMore:
|
||||||
|
INIA 0x01
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Everything up to and including the next line feed, thrown away. Used for the shebang.
|
||||||
|
scriptSkipLine:
|
||||||
|
CALL scriptByte
|
||||||
|
BNQ scriptSkipDone
|
||||||
|
SETD.1 ScriptChar
|
||||||
|
LDA.1
|
||||||
|
INIB 0d10
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BNQ scriptSkipLine
|
||||||
|
scriptSkipDone:
|
||||||
|
RET
|
||||||
|
|
||||||
|
scriptClose:
|
||||||
|
RSTA
|
||||||
|
SETD.1 ScriptRunning
|
||||||
|
STA.1
|
||||||
|
RET
|
||||||
|
|
||||||
|
#Data
|
||||||
|
|
||||||
|
ScriptRunning:
|
||||||
|
0x00
|
||||||
|
ScriptName:
|
||||||
|
#Reserve 0d64
|
||||||
|
ScriptBlocks:
|
||||||
|
0x00 0x00
|
||||||
|
ScriptIndex:
|
||||||
|
0x00 0x00
|
||||||
|
ScriptAt:
|
||||||
|
0x00 0x00
|
||||||
|
ScriptInto:
|
||||||
|
0x00 0x00
|
||||||
|
ScriptRoom:
|
||||||
|
0x00
|
||||||
|
ScriptLength:
|
||||||
|
0x00
|
||||||
|
ScriptChar:
|
||||||
|
0x00
|
||||||
|
ScriptBlock:
|
||||||
|
#Reserve 0d257
|
||||||
@@ -79,7 +79,7 @@ from `make`, not from here.
|
|||||||
### 1. Recorded output
|
### 1. Recorded output
|
||||||
|
|
||||||
`Tests/run.sh` assembles each program named in `Tests/manifest`, runs it, and compares
|
`Tests/run.sh` assembles each program named in `Tests/manifest`, runs it, and compares
|
||||||
everything it printed against a file in `Tests/expected`. 171 tests, of which 109 run, 35
|
everything it printed against a file in `Tests/expected`. 174 tests, of which 112 run, 35
|
||||||
only assemble, 16 are expected to fail to assemble, and 11 boot from ROM with no image
|
only assemble, 16 are expected to fail to assemble, and 11 boot from ROM with no image
|
||||||
given at all.
|
given at all.
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,12 @@ Keys.sbx 664
|
|||||||
Say.sbx 156
|
Say.sbx 156
|
||||||
Break.sbx 149
|
Break.sbx 149
|
||||||
notes.txt 21
|
notes.txt 21
|
||||||
8 files
|
hi.script 87
|
||||||
|
bad.script 45
|
||||||
|
plain.script 24
|
||||||
|
cross.script 280
|
||||||
|
nonl.script 38
|
||||||
|
13 files
|
||||||
> load what?
|
> load what?
|
||||||
> no such file
|
> no such file
|
||||||
> not a program
|
> not a program
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
CosmOS
|
||||||
|
> > Say from a script
|
||||||
|
it says: from a script
|
||||||
|
finished
|
||||||
|
> Say and again
|
||||||
|
it says: and again
|
||||||
|
finished
|
||||||
|
> halted
|
||||||
|
Execution halted.
|
||||||
|
[exit 0]
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
CosmOS
|
||||||
|
> > Say before
|
||||||
|
it says: before
|
||||||
|
finished
|
||||||
|
> nosuchcommand
|
||||||
|
I do not know: nosuchcommand
|
||||||
|
stopped: that line did not work
|
||||||
|
> do: that is not a script - it wants #! on the first line
|
||||||
|
> do: cannot find it
|
||||||
|
> do: give me the name of a script
|
||||||
|
> halted
|
||||||
|
Execution halted.
|
||||||
|
[exit 0]
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
CosmOS
|
||||||
|
> > Say across the block boundary
|
||||||
|
it says: across the block boundary
|
||||||
|
finished
|
||||||
|
> > Say with no newline after me
|
||||||
|
it says: with no newline after me
|
||||||
|
finished
|
||||||
|
> halted
|
||||||
|
Execution halted.
|
||||||
|
[exit 0]
|
||||||
@@ -7,7 +7,12 @@ Keys.sbx 664
|
|||||||
Say.sbx 156
|
Say.sbx 156
|
||||||
Break.sbx 149
|
Break.sbx 149
|
||||||
notes.txt 21
|
notes.txt 21
|
||||||
8 files
|
hi.script 87
|
||||||
|
bad.script 45
|
||||||
|
plain.script 24
|
||||||
|
cross.script 280
|
||||||
|
nonl.script 38
|
||||||
|
13 files
|
||||||
> loaded, starting at 4000
|
> loaded, starting at 4000
|
||||||
> it says: the disk took its time
|
> it says: the disk took its time
|
||||||
finished
|
finished
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
do hi.script
|
||||||
|
exit
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
do bad.script
|
||||||
|
do plain.script
|
||||||
|
do nothere.script
|
||||||
|
do
|
||||||
|
exit
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
do cross.script
|
||||||
|
do nonl.script
|
||||||
|
exit
|
||||||
@@ -109,6 +109,33 @@ for i in 1 2 3 4 5 6 7 8; do "$TOOL" put "$DISKS/sbfs.img" "filler$i.txt" >/dev/
|
|||||||
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
|
||||||
|
|
||||||
|
# ---- Scripts, including the ones that are meant to go wrong ----
|
||||||
|
#
|
||||||
|
# Built here rather than committed, because two of them are about BYTE POSITIONS and a
|
||||||
|
# committed file is one an editor can helpfully repair. cross.script puts a command across
|
||||||
|
# the 256 byte boundary between two blocks, and nonl.script ends without a newline; both
|
||||||
|
# were real faults before they were checks, and both are invisible in a text editor.
|
||||||
|
printf '#! script\n; A comment, which never reaches the shell.\nSay from a script\n\nSay and again\n' > hi.script
|
||||||
|
"$TOOL" put "$DISKS/cosmos.img" hi.script >/dev/null
|
||||||
|
# Stops at the bad line, so "after" must not appear.
|
||||||
|
printf '#! script\nSay before\nnosuchcommand\nSay after\n' > bad.script
|
||||||
|
"$TOOL" put "$DISKS/cosmos.img" bad.script >/dev/null
|
||||||
|
# No #! on the front, so it is a text file and not a script.
|
||||||
|
printf 'Say this has no shebang\n' > plain.script
|
||||||
|
"$TOOL" put "$DISKS/cosmos.img" plain.script >/dev/null
|
||||||
|
# A command that starts in the first block and finishes in the second.
|
||||||
|
python3 -c "
|
||||||
|
head = '#! script\n'
|
||||||
|
pad = ''
|
||||||
|
while len(head) + len(pad) < 246:
|
||||||
|
pad += '; pad\n'
|
||||||
|
open('cross.script','w').write(head + pad + 'Say across the block boundary\n')
|
||||||
|
"
|
||||||
|
"$TOOL" put "$DISKS/cosmos.img" cross.script >/dev/null
|
||||||
|
# The last line has no newline after it and still has to run.
|
||||||
|
printf '#! script\nSay with no newline after me' > nonl.script
|
||||||
|
"$TOOL" put "$DISKS/cosmos.img" nonl.script >/dev/null
|
||||||
|
|
||||||
# A disk of its own for the writing test, with one file already on it so that what it
|
# A disk of its own for the writing test, with one file already on it so that what it
|
||||||
# writes has to be placed somewhere that does not tread on what is there.
|
# writes has to be placed somewhere that does not tread on what is there.
|
||||||
"$TOOL" format "$DISKS/write.img" 32 1 >/dev/null
|
"$TOOL" format "$DISKS/write.img" 32 1 >/dev/null
|
||||||
|
|||||||
@@ -762,6 +762,19 @@ waitTest | testPrograms/waitTest.asm | run | -
|
|||||||
# Interrupt Flag with no handler installed anywhere: a standing line faults there, which is
|
# Interrupt Flag with no handler installed anywhere: a standing line faults there, which is
|
||||||
# how a program loaded by CosmOS used to die on the instruction after its SIF.
|
# how a program loaded by CosmOS used to die on the instruction after its SIF.
|
||||||
diskLineTest | testPrograms/diskLineTest.asm | run | - | 1000000 | disks/sbfs.img
|
diskLineTest | testPrograms/diskLineTest.asm | run | - | 1000000 | disks/sbfs.img
|
||||||
|
# ---- Scripts ----
|
||||||
|
#
|
||||||
|
# The shell reading its lines out of a file instead of off the console. cosmosScript is the
|
||||||
|
# ordinary case and also checks that comments and blank lines never reach the dispatch, and
|
||||||
|
# that a script running out hands back to the console rather than ending the shell.
|
||||||
|
cosmosScript | CosmOS/Source/cosmos.asm | run | cosmosScript.in | 200000000 | disks/cosmos.img
|
||||||
|
# The three refusals and the stop. A line that fails stops the script - "after" must not be
|
||||||
|
# in the recording - and a file with no #! is not a script however it is named.
|
||||||
|
cosmosScriptBad | CosmOS/Source/cosmos.asm | run | cosmosScriptBad.in | 200000000 | disks/cosmos.img
|
||||||
|
# The two that are about byte positions rather than about behaviour: a command lying across
|
||||||
|
# the boundary between two blocks, and a last line with no newline after it. Both of these
|
||||||
|
# were faults before they were checks.
|
||||||
|
cosmosScriptEdges | CosmOS/Source/cosmos.asm | run | cosmosScriptEdges.in | 200000000 | disks/cosmos.img
|
||||||
printDecimalTest | testPrograms/printDecimalTest.asm | xfail | - | -
|
printDecimalTest | testPrograms/printDecimalTest.asm | xfail | - | -
|
||||||
printDigitTest | testPrograms/printDigitTest.asm | xfail | - | -
|
printDigitTest | testPrograms/printDigitTest.asm | xfail | - | -
|
||||||
printHexTest | testPrograms/printHexTest.asm | xfail | - | -
|
printHexTest | testPrograms/printHexTest.asm | xfail | - | -
|
||||||
|
|||||||
+8
-1
@@ -128,7 +128,14 @@ done
|
|||||||
# not to exercise; a binary that BUILDS ITSELF AGAIN and comes out the same has been through
|
# not to exercise; a binary that BUILDS ITSELF AGAIN and comes out the same has been through
|
||||||
# its own machinery. The host is no longer necessary after this point - it is a convenience.
|
# its own machinery. The host is no longer necessary after this point - it is a convenience.
|
||||||
|
|
||||||
SOURCES="cosmos console sbfs services text"
|
# ---- Read out of cosmos.asm rather than listed here ----
|
||||||
|
#
|
||||||
|
# Everything the system includes has to be on the disk it is going to be assembled on. This
|
||||||
|
# was a hand written list and went stale the moment script.asm was added: the self build
|
||||||
|
# stopped, saying it could not find a file nobody had thought to put there. That is the
|
||||||
|
# fourth time a list beside a thing has drifted from the thing, so it is now the thing that
|
||||||
|
# is asked.
|
||||||
|
SOURCES="cosmos $(sed -n 's/^#Include \(.*\)\.asm$/\1/p' "$ROOT/Programs/CosmOS/Source/cosmos.asm" | tr '\n' ' ')"
|
||||||
PARTS="Asm numbers source token classify labels vectors table"
|
PARTS="Asm numbers source token classify labels vectors table"
|
||||||
|
|
||||||
"$TOOL" format "$WORK/self.img" 4096 8 >/dev/null
|
"$TOOL" format "$WORK/self.img" 4096 8 >/dev/null
|
||||||
|
|||||||
@@ -574,7 +574,7 @@ $(COSMOS_DISK): $(APPS) $(NATIVE_ASM) $(COSMOS) $(STAGE2) \
|
|||||||
@# read on the machine and not assembled on it, and the assembler said only "nothing was
|
@# read on the machine and not assembled on it, and the assembler said only "nothing was
|
||||||
@# written". Mirrored, so that the next one nobody thinks of is here anyway.
|
@# written". Mirrored, so that the next one nobody thinks of is here anyway.
|
||||||
$(DISKTOOL) mirror $@ $(PROG_DIR)/Libraries /Lib
|
$(DISKTOOL) mirror $@ $(PROG_DIR)/Libraries /Lib
|
||||||
@for f in console fileStream sbfs services text config; do \
|
@for f in console fileStream sbfs services text config script; do \
|
||||||
$(DISKTOOL) put $@ $(PROG_DIR)/CosmOS/Source/$$f.asm /Lib/$$f.asm >/dev/null \
|
$(DISKTOOL) put $@ $(PROG_DIR)/CosmOS/Source/$$f.asm /Lib/$$f.asm >/dev/null \
|
||||||
|| exit 1; done
|
|| exit 1; done
|
||||||
@for f in classify labels numbers scratch source table token vectors; do \
|
@for f in classify labels numbers scratch source table token vectors; do \
|
||||||
|
|||||||
Reference in New Issue
Block a user