Loops, and the scripting language is a language

while and for. Both only mean anything in a script, because a loop goes back to
the line that opened it and a prompt has no line to go back to - and both say so
rather than doing something surprising.

THE SCRIPT READER KEEPS THE POSITION OF EVERY LINE before reading it, which is
what makes any of this possible: by the time a line has been read the reader is
past it, and a line is not a fixed size to subtract. Three words per line, and
the block is read again on the way back so the pointer into it means what it
meant - the same thing nesting one script inside another already did, for a
different reason.

THE TWO LOOPS END DIFFERENTLY, and that is the design rather than an accident. A
while is taken away at its end and its own line asks the question again, so
nothing has to be remembered. A for is not: how many words it has used is kept
in the block, and its line reads itself again and counts one more off the front.
That is a byte in a block instead of a copy of the word list in every one of
them.

Blocks grew from a byte to a record of sixteen - state, kind, words used, and
where the line that opened it was - and sixteen because A and B are a shift
register, so four rotations turn a block number into its offset. The history and
the variables are addressed the same way for the same reason.

Nested loops, an if inside a loop, a loop inside a branch nobody takes, and a for
with no words: the last two run no times rather than once, which is the case
worth having a test for.

Three things found by running it:

textSame asks whether two WHOLE strings are the same, so "in red green blue" is
not "in". The word has to be split off before it is compared.

A for typed at a prompt complained about while, because both arrive at the same
place. One message that names neither is better than one that names the wrong
one.

And docs.sh caught a naming convention nobody had written down: it recognises a
packed name by its label ending in "Name", so ForName2 was silently not counted.
It failed the right way round - saying the run was shorter than the count claims
rather than passing - but the convention now lives where the names are and not
only in the checker.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-09-01 20:50:05 -04:00
co-authored by Claude Opus 5
parent 16f8232a35
commit 2abc8281df
16 changed files with 522 additions and 54 deletions
+28
View File
@@ -138,6 +138,32 @@ The shell already had the other half. `LineFailed` exists because a script stops
line that did not work, so every command in the system was already saying whether it had
worked, for a different reason.
**And two loops, which only mean anything in a script**, because a loop goes back to the line
that opened it and a prompt has no line to go back to.
```text
#! script
for colour in red green blue
echo it is $colour
end
set n go
while same $n go
echo round once
set n stop
end
```
The script reader keeps the position of **every** line before reading it, which is what makes
that possible: by the time a line has been read the reader is past it, and a line is not a
fixed size to subtract.
A `while` is taken away at its `end` and its line asks the question again. A `for` is not:
how many words it has used is kept in the block, and its line reads itself again and counts
one more off the front - a byte in a block rather than a copy of the list in every one of
them. A `for` with no words runs no times, and a loop inside a branch nobody is taking runs
no times either.
Blocks nest eight deep. A branch that is not being taken is **not even looked at**: the
skipping happens before names are filled in, so `$whatever` inside a branch nobody is running
is not a mistake, and a line nobody is running cannot fail.
@@ -305,6 +331,8 @@ CosmOS currently provides these built-in commands:
| `if <command>` | Run the lines after it only if that command worked. See Lines Run Sometimes. |
| `else` | Run them only if it did not. |
| `end` | Close the block. |
| `while <command>` | Run the lines after it for as long as that command keeps working. |
| `for <name> in <words>` | Run them once for each word, with the name set to it. |
| `same <a> <b>` | Fails when the two are different, which is how `if` asks about a value. |
| `monitor` | Enter monitor mode, in which the prompt becomes `*` and the commands below are also available. |
| `help` | Show the built-in command summary. |
+351 -45
View File
@@ -208,6 +208,15 @@ promptIfTaken:
promptIfPush:
CALL blockPush
BNQ ifDeep
; A loop has to be able to come back to the line that asked, which if does not.
SETD.1 LoopKind
LDA.1
BRA promptIfPlain
CALL blockTop
BNQ promptIfPlain
CALL blockKeepWhere
promptIfPlain:
RSTA
SETD.1 LineFailed
STA.1 ; And the question is answered rather than unanswered.
@@ -388,6 +397,16 @@ promptDispatch:
CALL textSame
BRQ doSame
SETD.0 CommandLine
SETD.1 WhileName
CALL textSame
BRQ doWhile
SETD.0 CommandLine
SETD.1 ForName
CALL textSame
BRQ doFor
SETD.0 CommandLine
SETD.1 HelpName
CALL textSame
@@ -2790,22 +2809,52 @@ varExpandNoSuch:
; and the shell already had the other half - LineFailed, which every command sets and which
; stop-on-failure was built on.
;
; A block is one byte on a stack eight deep:
; A block is a record of sixteen bytes, eight of them, and sixteen because A and B are a
; shift register: four rotations turn a block number into its offset. The history and the
; variables are addressed the same way and for the same reason.
;
; 0 this branch is running
; 1 this branch is not, but an else would turn it on
; 2 this branch is not, and an else would not either
; +0 state: 0 running, 1 not but an else would, 2 not and an else would not
; +1 kind: 0 if, 1 while, 2 for
; +2 how many words a for has used
; +4 where the line that opened it was, as a script position - see blockKeepWhere
;
; The two kinds of not-running are what make nesting work without looking down the stack: an
; "if" met while something above it is being skipped pushes a 2, so the top of the stack
; always says everything, and a line runs when the stack is empty or its top is nought.
blockSkipping:
; DP3 to block A.
blockAt:
SETD.3 BlockStack
RSTB
SHR
SHR
SHR
SHR
DPUW.3
RET
; DP3 on the block on top. Q is one if there is not one.
blockTop:
SETD.1 BlockDepth
LDA.1
BRA blockRunning ; Nothing open, so nothing is being skipped.
BRA blockNone
DECA
SETD.3 BlockStack
DPUA.3
CALL blockAt
RSTA
RSTB
CCF
ADD
RET
blockNone:
INIA 0x01
RSTB
CCF
ADD
RET
blockSkipping:
CALL blockTop
BNQ blockRunning ; Nothing open, so nothing is being skipped.
LDA.3
BRA blockRunning
INIA 0x01
@@ -2820,7 +2869,8 @@ blockRunning:
ADD ; Q is zero: running.
RET
; A holds what to push. Q is one if there is no room for another.
; A holds the state to push and LoopKind what kind of block it is. Q is one if there is no
; room for another.
blockPush:
SETD.1 BlockHold
STA.1
@@ -2830,12 +2880,20 @@ blockPush:
CCF
SUB
BNC blockFull
LDA.1
SETD.3 BlockStack
DPUA.3
CALL blockAt
SETD.1 BlockHold
LDA.1
STA.3
INCD.3
SETD.1 LoopKind
LDA.1
STA.3
INCD.3
RSTA
STA.3 ; No words used yet, whatever kind it is.
SETD.1 BlockDepth
LDA.1
INCA
@@ -2852,24 +2910,58 @@ blockFull:
ADD
RET
; DP3 on the block on top. Q is one if there is not one.
blockTop:
blockPop:
SETD.1 BlockDepth
LDA.1
BRA blockNone
DECA
SETD.3 BlockStack
DPUA.3
RSTA
RSTB
CCF
ADD
STA.1
RET
blockNone:
INIA 0x01
RSTB
CCF
ADD
; ---- Going back to the line that opened a block ----
;
; A loop is a block that, when it ends, puts the reader back where it started. The position
; was kept before the line was read - see ScriptLineIndex in script.asm, because by the time
; a line has been read the reader is past it and a line is not a fixed size to subtract. The
; block it names is read again on the way back, which is what makes the pointer into it mean
; what it meant. Exactly what nesting one script inside another already does, for a different
; reason.
;
; DP3 names the block.
blockKeepWhere:
SETD.0 ScriptLineIndex
PSHD.3
POPD.1
DPUP.1 0d4
CALL sbfsCopyWord
SETD.0 ScriptLineAt
PSHD.3
POPD.1
DPUP.1 0d6
CALL sbfsCopyWord
SETD.0 ScriptLineBlocks
PSHD.3
POPD.1
DPUP.1 0d8
CALL sbfsCopyWord
RET
blockGoWhere:
PSHD.3
POPD.0
DPUP.0 0d4
SETD.1 ScriptIndex
CALL sbfsCopyWord
PSHD.3
POPD.0
DPUP.0 0d6
SETD.1 ScriptAt
CALL sbfsCopyWord
PSHD.3
POPD.0
DPUP.0 0d8
SETD.1 ScriptBlocks
CALL sbfsCopyWord
CALL scriptReread
RET
; ---- if ----
@@ -2879,13 +2971,17 @@ blockNone:
; somebody's answer. The note is read at the top of the loop, which is where every command
; comes back to and the one place that sees a result before the next line disturbs it.
doIf:
RSTA
SETD.1 LoopKind
STA.1
doIfKind:
CALL blockSkipping
BNQ ifSkipped
SETD.1 TextRest
LDD.0.1
LDA.0
BRA ifWhat ; "if" with nothing to decide by.
BRA ifWhat ; Nothing to decide by.
; The rest of the line, moved to the front of it. Forwards, and the source is ahead of the
; destination, so one walk does it without anything being overwritten before it is read.
@@ -2914,15 +3010,29 @@ ifSkipped:
ifWhat:
SETD.0 IfUsage
CALL printString
CALL newLine
BRI commandFailed
BRI blockComplain
ifDeep:
SETD.0 IfTooDeep
CALL printString
CALL newLine
BRI commandFailed
BRI blockComplain
; ---- while ----
;
; The same shape as if, and then one thing more: the block remembers where the while line
; was, and end goes back to it - so the condition is ASKED AGAIN rather than remembered.
doWhile:
SETD.1 ScriptDepth
LDA.1
BRA loopTyped ; There is nothing to go back to at a prompt.
INIA 0d1
SETD.1 LoopKind
STA.1
BRI doIfKind
; Neither loop means anything typed at a prompt: there is no line to go back to. Said once,
; because both of them arrive here and a message naming the wrong one is worse than none.
loopTyped:
SETD.0 LoopTyped
BRI blockComplain
; ---- else ----
doElse:
@@ -2943,23 +3053,174 @@ elseStays:
BRI prompt
elseLonely:
SETD.0 ElseLonely
CALL printString
CALL newLine
BRI commandFailed
BRI blockComplain
; ---- end ----
;
; What it does depends on what it closes. An if is simply taken away. A loop that was running
; goes back to the line that opened it - a while so that its condition is asked again, and a
; for so that its next word is taken - and a loop that was not running has finished, so it
; goes away like anything else.
doEnd:
SETD.1 BlockDepth
LDA.1
BRA endLonely
DECA
CALL blockTop
BNQ endLonely
LDA.3
BNA endAway ; Not running, so whatever it was, it is over.
INCD.3
LDA.3
BRA endAway ; An if that ran needs nothing doing.
INIB 0d2
XOR
BRQ endForAgain
; A while: taken away, and the reader put back on its line. The line pushes a fresh block
; when it asks its question again.
CALL blockTop
CALL blockGoWhere
CALL blockPop
BRI prompt
endForAgain:
; A for: the block stays, because what it has used is in it, and the line it opened with
; reads itself again and counts off one more word.
INIA 0x01
SETD.1 LoopResume
STA.1
CALL blockTop
CALL blockGoWhere
BRI prompt
endAway:
CALL blockPop
BRI prompt
endLonely:
SETD.0 EndLonely
CALL printString
CALL newLine
BRI commandFailed
BRI blockComplain
; ---- for ----
;
; "for x in a b c" runs what follows once for each word, with x set to it in turn. THE LINE IS
; READ AGAIN ON EVERY TURN and the words counted off from the front, so the only thing a block
; has to remember is how many have been used - one byte, rather than a copy of the list in
; every block.
doFor:
SETD.1 ScriptDepth
LDA.1
BRA loopTyped
SETD.1 LoopResume
LDA.1
BNA forAgain
CALL blockSkipping
BNQ ifSkipped
; A new one. It is pushed running, and set to not-running below if there are no words.
INIA 0d2
SETD.1 LoopKind
STA.1
RSTA
CALL blockPush
BNQ ifDeep
CALL blockTop
BNQ forLonely
CALL blockKeepWhere
BRI forWord
forAgain:
; Coming round again: the block that is open already says how many words have gone.
RSTA
SETD.1 LoopResume
STA.1
CALL blockTop
BNQ forLonely
INIA 0d2
DPUA.3
LDA.3
INCA
STA.3
forWord:
CALL blockTop
BNQ forLonely
INIA 0d2
DPUA.3
LDA.3
SETD.1 ForUsed
STA.1
SETD.1 TextRest
LDD.0.1
SETD.1 ForVarAt
STD.0.1
CALL textSplit
; The word "in" between the name and the words. Split off first and then compared, because
; textSame asks whether two whole strings are the same and "in red green blue" is not "in".
SETD.1 TextRest
LDD.0.1
LDA.0
BRA forWhat
CALL textSplit
SETD.1 InName
CALL textSame
BNQ forWhat
forSkipWords:
SETD.1 ForUsed
LDA.1
BRA forTakeWord
DECA
STA.1
SETD.1 TextRest
LDD.0.1
LDA.0
BRA forNoMore
CALL textSplit
BRI forSkipWords
forTakeWord:
SETD.1 TextRest
LDD.0.1
LDA.0
BRA forNoMore
SETD.1 ForWordAt
STD.0.1
CALL textSplit
SETD.0 ForVarAt
LDD.0.0
SETD.1 ForWordAt
LDD.1.1
CALL varSet
BNQ forFull
CALL blockTop
BNQ forLonely
RSTA
STA.3 ; Running, and the body follows.
BRI prompt
forNoMore:
; The words have run out, so the body is passed over and the end takes the block away.
CALL blockTop
BNQ forLonely
INIA 0d1
STA.3
BRI prompt
forWhat:
SETD.0 ForUsage
BRI blockComplain
forFull:
SETD.0 SetVarNoRoom
BRI blockComplain
forLonely:
SETD.0 EndLonely
BRI blockComplain
; ---- same ----
;
@@ -2982,6 +3243,13 @@ doSame:
sameNot:
BRI commandFailed
; DP0 names what went wrong. Says it and marks the line, which is what every one of these
; does and is worth writing once.
blockComplain:
CALL printString
CALL newLine
BRI commandFailed
; Takes the spaces off the front of the line, by moving what is after them to the front. In
; place and forwards, with the source ahead of the destination, so one walk does it.
lineTrim:
@@ -3059,6 +3327,16 @@ blockPassOver:
CALL lineFirstIs
BRQ ifSkipped
SETD.0 CommandLine
SETD.1 WhileName
CALL lineFirstIs
BRQ ifSkipped
SETD.0 CommandLine
SETD.1 ForName
CALL lineFirstIs
BRQ ifSkipped
SETD.0 CommandLine
SETD.1 ElseName
CALL lineFirstIs
@@ -7274,6 +7552,11 @@ NeedsValue:
; NOTHING MAY BE PUT BETWEEN THEM. Tests/docs.sh checks that this run holds exactly the names
; the dispatch tests and that the count below agrees, so a command added without a name here
; is caught rather than silently left out of what Tab knows about.
;
; AND EACH LABEL ENDS IN "Name", which is how that check recognises one. A label called
; ForName2 was not counted, and the check said the run was one shorter than the count claimed
; rather than saying why - which is the right way round for it to fail, but the convention is
; worth writing down where the names are rather than only in the checker.
ShellNames:
DirName:
"dir"
@@ -7315,10 +7598,14 @@ EndName:
"end"
SameName:
"same"
WhileName:
"while"
ForName:
"for"
; How many of them, since a run of strings does not say where it stops.
ShellNameCount:
0d20
0d22
SbxText:
".sbx"
@@ -7342,6 +7629,12 @@ ElseLonely:
"else with no if above it"
EndLonely:
"end with no if above it"
LoopTyped:
"a loop wants a script to go back through"
ForUsage:
"for wants a name, then in, then the words"
InName:
"in"
; Where the machine is, written out for the prompt, and where in the buffer it begins.
; Built from the end backwards, so it starts somewhere in the middle.
@@ -7627,11 +7920,24 @@ ShellStack:
; ---- Which blocks are open, and whether their lines are being run ----
BlockStack:
#Reserve 0d8
#Reserve 0d128
BlockDepth:
0x00
BlockHold:
0x00
; What kind of block is about to be pushed, since blockPush takes the state in A and there is
; nowhere else for a second answer to go.
LoopKind:
0x00
; Set by an end that is sending a for round again, and read by the for line it lands on.
LoopResume:
0x00
ForUsed:
0x00
ForVarAt:
0x00 0x00
ForWordAt:
0x00 0x00
; Set while the line being run is an "if" condition rather than somebody's own command.
IfPending:
0x00
+28
View File
@@ -243,6 +243,21 @@ scriptLine:
STD.0.1
scriptLineAgain:
; ---- Where this line begins ----
;
; Kept before it is read, because a loop has to be able to go back to the line that opened
; it and by the time that line has been read the reader is past it. Three words, and the
; block itself is read again on the way back, which is what scriptReread is for.
SETD.0 ScriptIndex
SETD.1 ScriptLineIndex
CALL sbfsCopyWord
SETD.0 ScriptAt
SETD.1 ScriptLineAt
CALL sbfsCopyWord
SETD.0 ScriptBlocks
SETD.1 ScriptLineBlocks
CALL sbfsCopyWord
SETD.1 ScriptLength
RSTA
STA.1
@@ -539,6 +554,19 @@ ScriptIndex:
0x00 0x00
ScriptAt:
0x00 0x00
; ---- And where the line being read began ----
;
; A loop goes back to the line that opened it, and by the time that line has been read the
; reader is past it. So the position is kept before every line rather than worked out
; afterwards, which cannot be done: a line is not a fixed size and there is nothing to
; subtract.
ScriptLineIndex:
0x00 0x00
ScriptLineAt:
0x00 0x00
ScriptLineBlocks:
0x00 0x00
; Saved with the rest, so that a quiet script calling a loud one gets its quiet back when
; the loud one finishes. A new script INHERITS it rather than resetting, because a build
; that asked for quiet meant its helpers too; only the first script started from the prompt
+1 -1
View File
@@ -79,7 +79,7 @@ from `make`, not from here.
### 1. Recorded output
`Tests/run.sh` assembles each program named in `Tests/manifest`, runs it, and compares
everything it printed against a file in `Tests/expected`. 202 tests, of which 140 run, 35
everything it printed against a file in `Tests/expected`. 203 tests, of which 141 run, 35
only assemble, 16 are expected to fail to assemble, and 11 boot from ROM with no image
given at all.
+2 -1
View File
@@ -25,6 +25,7 @@ Mode.sbx 48
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
tune.sbx 306
notes.txt 21
Apps <dir>
@@ -37,7 +38,7 @@ outer.script 376
inner.script 44
loop.script 35
crossed.txt 560
24 files, 1 directory
25 files, 1 directory
> exit
halted
Execution halted.
+2 -1
View File
@@ -15,6 +15,7 @@ Mode.sbx 48
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
tune.sbx 306
notes.txt 21
Apps <dir>
@@ -26,7 +27,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
23 files, 1 directory
24 files, 1 directory
> drive 1
> dir
other.txt 28
+2 -1
View File
@@ -32,6 +32,7 @@ Mode.sbx 48
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
tune.sbx 306
notes.txt 21
Apps <dir>
@@ -43,7 +44,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
23 files, 1 directory
24 files, 1 directory
> exit
halted
Execution halted.
+2 -1
View File
@@ -22,6 +22,7 @@ Mode.sbx 48
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
tune.sbx 306
notes.txt 21
Apps <dir>
@@ -33,7 +34,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
23 files, 1 directory
24 files, 1 directory
> exit
halted
Execution halted.
+77
View File
@@ -0,0 +1,77 @@
CosmOS
> do loops.script
> for a in 1 2
> for b in x y
> echo $a$b
1x
> end
> for b in x y
> echo $a$b
1y
> end
> for b in x y
> echo $a$b
> end
> end
> for a in 1 2
> for b in x y
> echo $a$b
2x
> end
> for b in x y
> echo $a$b
2y
> end
> for b in x y
> echo $a$b
> end
> end
> for a in 1 2
> for b in x y
> echo $a$b
> end
> end
> set n go
> while same $n go
> echo round
round
> set n stop
> end
> while same $n go
> echo round
> set n stop
> end
> for c in p q
> if same $c p
> echo only p
only p
> end
> end
> for c in p q
> if same $c p
> echo only p
> end
> end
> for c in p q
> if same $c p
> echo only p
> end
> end
> if same 1 2
> for d in never
> echo not this
> end
> end
> for e in
> echo no words
> end
> echo finished
finished
> while same a a
a loop wants a script to go back through
> for x in a b
a loop wants a script to go back through
> exit
halted
Execution halted.
[exit 0]
+2 -1
View File
@@ -108,6 +108,7 @@ Mode.sbx 48
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
tune.sbx 306
notes.txt 21
Apps <dir>
@@ -119,7 +120,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
23 files, 1 directory
24 files, 1 directory
> exit
halted
Execution halted.
+2 -1
View File
@@ -27,6 +27,7 @@ Mode.sbx 48
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
tune.sbx 306
notes.txt 21
Apps <dir>
@@ -38,7 +39,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
23 files, 1 directory
24 files, 1 directory
> exit
halted
Execution halted.
+2 -1
View File
@@ -15,6 +15,7 @@ Mode.sbx 48
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
tune.sbx 306
notes.txt 21
Apps <dir>
@@ -26,7 +27,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
23 files, 1 directory
24 files, 1 directory
> load
load what?
> load nosuch.sbx
+2 -1
View File
@@ -13,6 +13,7 @@ Mode.sbx 48
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
tune.sbx 306
notes.txt 21
Apps <dir>
@@ -24,7 +25,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
23 files, 1 directory
24 files, 1 directory
> load Say.sbx
loaded, starting at 5000
> run the disk took its time
+4
View File
@@ -0,0 +1,4 @@
do loops.script
while same a a
for x in a b
exit
+5
View File
@@ -138,6 +138,11 @@ printf '#! script\necho before\necho $nosuchname\necho after\n' > vars.script
printf '#! script\nset colour red\nif same $colour red\n echo it is red\n if same $colour blue\n echo and blue\n else\n echo but not blue\n end\nelse\n echo $neverSetAnywhere\nend\nif same $colour blue\n echo wrong\nelse\n echo right\nend\nif same $colour blue\n if same $colour red\n echo deep wrong\n else\n echo deep also wrong\n end\nend\necho done\n' > blocks.script
"$TOOL" put "$DISKS/cosmos.img" vars.script >/dev/null
"$TOOL" put "$DISKS/cosmos.img" blocks.script >/dev/null
# Loops, which are what a block is for once there is more than one way out of it. Nested, and
# with an if inside one and a loop inside a branch nobody takes - which must run no times at
# all rather than once.
printf '#! script\nfor a in 1 2\n for b in x y\n echo $a$b\n end\nend\nset n go\nwhile same $n go\n echo round\n set n stop\nend\nfor c in p q\n if same $c p\n echo only p\n end\nend\nif same 1 2\n for d in never\n echo not this\n end\nend\nfor e in\n echo no words\nend\necho finished\n' > loops.script
"$TOOL" put "$DISKS/cosmos.img" loops.script >/dev/null
# tune.sbx, which used to be a boot image and is now a program like any other. It brings a
# vector of its own - the screen's, so that it has a beat to play to - which makes it the
# second thing here that the version two format exists for.
+12
View File
@@ -438,6 +438,18 @@ cosmosStack | CosmOS/Source/cosmos.asm | run | cosmosSta
# set, inside the branch that is not taken, and that must not be an error - so the skipping
# happens before the names are filled in, and a line nobody is running cannot fail.
cosmosBlocks | CosmOS/Source/cosmos.asm | run | cosmosBlocks.in | - | disks/cosmos.img
# And the two loops. Both go back to the line that opened them, which is why the script
# reader keeps the position of every line before reading it: by the time a line has been read
# the reader is past it, and a line is not a fixed size to subtract.
#
# A while is TAKEN AWAY at its end and its line asks the question again. A for is not: what it
# has used is in the block, and the line reads itself again and counts one more word off the
# front - which is a byte in a block rather than a copy of the list in each one.
#
# Nested loops, an if inside a loop, a loop inside a branch nobody takes, and a for with no
# words at all, which runs no times rather than once. Neither loop makes sense typed at a
# prompt, and both say so.
cosmosLoops | CosmOS/Source/cosmos.asm | run | cosmosLoops.in | - | disks/cosmos.img
# The same editing offered to a PROGRAM, through osReadLine. Edit reads its lines that way,
# so a word typed with two letters the wrong way round is put right without starting the line
# again - which is the whole of what A4 buys.