diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index 1dcd781..87c9061 100644 --- a/Programs/CosmOS/README.md +++ b/Programs/CosmOS/README.md @@ -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 ` | 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 ` | Run the lines after it for as long as that command keeps working. | +| `for in ` | Run them once for each word, with the name set to it. | | `same ` | 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. | diff --git a/Programs/CosmOS/Source/cosmos.asm b/Programs/CosmOS/Source/cosmos.asm index 9a479df..842d4ee 100644 --- a/Programs/CosmOS/Source/cosmos.asm +++ b/Programs/CosmOS/Source/cosmos.asm @@ -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 diff --git a/Programs/CosmOS/Source/script.asm b/Programs/CosmOS/Source/script.asm index 9b033e6..940649d 100644 --- a/Programs/CosmOS/Source/script.asm +++ b/Programs/CosmOS/Source/script.asm @@ -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 diff --git a/SplitBit Test Manual.md b/SplitBit Test Manual.md index 897e06e..5070174 100644 --- a/SplitBit Test Manual.md +++ b/SplitBit Test Manual.md @@ -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. diff --git a/Tests/expected/cosmosCrossDisk.out b/Tests/expected/cosmosCrossDisk.out index a116723..1cd9a3e 100644 --- a/Tests/expected/cosmosCrossDisk.out +++ b/Tests/expected/cosmosCrossDisk.out @@ -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 @@ -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. diff --git a/Tests/expected/cosmosDrives.out b/Tests/expected/cosmosDrives.out index 6e24375..5ed403e 100644 --- a/Tests/expected/cosmosDrives.out +++ b/Tests/expected/cosmosDrives.out @@ -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 @@ -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 diff --git a/Tests/expected/cosmosFault.out b/Tests/expected/cosmosFault.out index 2e7d861..7bb9e29 100644 --- a/Tests/expected/cosmosFault.out +++ b/Tests/expected/cosmosFault.out @@ -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 @@ -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. diff --git a/Tests/expected/cosmosGrid.out b/Tests/expected/cosmosGrid.out index 618e1a7..3622678 100644 --- a/Tests/expected/cosmosGrid.out +++ b/Tests/expected/cosmosGrid.out @@ -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 @@ -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. diff --git a/Tests/expected/cosmosLoops.out b/Tests/expected/cosmosLoops.out new file mode 100644 index 0000000..5a29f4b --- /dev/null +++ b/Tests/expected/cosmosLoops.out @@ -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] diff --git a/Tests/expected/cosmosMonitor.out b/Tests/expected/cosmosMonitor.out index de88c48..acca83a 100644 --- a/Tests/expected/cosmosMonitor.out +++ b/Tests/expected/cosmosMonitor.out @@ -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 @@ -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. diff --git a/Tests/expected/cosmosMonitorRun.out b/Tests/expected/cosmosMonitorRun.out index e607ba2..97a24b1 100644 --- a/Tests/expected/cosmosMonitorRun.out +++ b/Tests/expected/cosmosMonitorRun.out @@ -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 @@ -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. diff --git a/Tests/expected/cosmosRun.out b/Tests/expected/cosmosRun.out index 4d6e679..5f3486b 100644 --- a/Tests/expected/cosmosRun.out +++ b/Tests/expected/cosmosRun.out @@ -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 @@ -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 diff --git a/Tests/expected/cosmosSlowDisk.out b/Tests/expected/cosmosSlowDisk.out index 0c5317a..e4043b2 100644 --- a/Tests/expected/cosmosSlowDisk.out +++ b/Tests/expected/cosmosSlowDisk.out @@ -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 @@ -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 diff --git a/Tests/input/cosmosLoops.in b/Tests/input/cosmosLoops.in new file mode 100644 index 0000000..c81a15b --- /dev/null +++ b/Tests/input/cosmosLoops.in @@ -0,0 +1,4 @@ +do loops.script +while same a a +for x in a b +exit diff --git a/Tests/makedisks.sh b/Tests/makedisks.sh index 7d784dd..d41ebbb 100755 --- a/Tests/makedisks.sh +++ b/Tests/makedisks.sh @@ -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. diff --git a/Tests/manifest b/Tests/manifest index a22ec8a..5f605f7 100644 --- a/Tests/manifest +++ b/Tests/manifest @@ -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.