bcd42e75ca184ae33a28319e5b7defeb397ee3ad
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
553882d28d |
Start CosmOS with a script, and let a script hold its tongue
Three things scripts wanted, and they are one thing: a machine that can have a face. /System/Boot/startup.sh runs before anybody can type. Every way of reaching the prompt for the first time goes through it, including the one where there is no disk - in which case there is nothing to find and nothing is said. A MISSING one is ordinary and silent, because a clean install has none and a machine that complained every boot about a file nobody wrote would be teaching its owner to ignore it. One that is THERE and does not begin with #! is the other case entirely: somebody meant that to run. #quiet stops each line being echoed, #loud puts it back. The prompt and the echo go together, because together they are what makes a script look like typing, so a quiet script gets neither and what it prints is all that appears. A nested script inherits quiet - a build that asked for it meant its helpers too - and gets its own setting back when the helper returns. Anything else beginning with # is handed to the shell, which does not know it and stops the script, because a script that asked for something this shell cannot do should not carry on as though it had been given it. clear empties the screen, which the console has been able to do since before there was a screen to do it on. THE PROMPT IS NOW SAID BY WHOEVER SUPPLIES THE LINE. It used to be said at the top of the loop, which is a decision made before the line is read and an answer not known until after - and it was wrong at both ends. #quiet is itself a line, so its prompt went out before anything knew to stay silent; and the line after a quiet script's last one comes from the console, having already been denied one. Off by exactly one line in opposite directions. A first attempt at this remembered whether the prompt had been skipped, which worked and was a flag standing in for a structure. The monitor's assembler prints a prompt of its own, so it reads through shellReadRaw, which is the same source without one. One admission. Handing the console its prompt back when a quiet script ended was a real fix when I wrote it and stopped being one an hour later, because the restructure above means the console's own path prompts whatever the flag holds. The comment claimed it fixed something. Breaking it on purpose changed nothing, which is how that was found, and it is now a comment saying so instead of a line pretending to work. The startup fixture ends QUIET on purpose: nothing puts the flag back when the outermost script finishes, so a script ending #loud would have tested the easy half. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW |
||
|
|
c28826df77 |
Let a script run a script, four deep
A build script calling a setup script is the first thing anybody tries. What is saved when one script starts another is A POSITION AND NOT A BUFFER: the name, which block comes next, how many are left, and where in the block it had got to. Seventy bytes, and they sit next to each other in the data segment on purpose so that saving them is one copy. The block itself is read again on the way back, which costs one disk read per return and saves 257 bytes a level - the inner script reads its own block into the single buffer there is, so coming back means fetching the outer one's block again and landing on the byte it left. The slot is reached by stepping rather than by multiplying, because this machine has no multiply and the depth is never more than three steps. Four levels. Deep enough for a script calling a script that calls a helper, shallow enough that a script running itself says so rather than filling memory. A line that fails now stops every level and not just the innermost, because a build whose helper failed should not carry on in its caller. The caller's place is saved BEFORE the new file is looked at, and put back on every way out that is not success. Opening writes the name into the live state in order to ask the disk about it, so by the time "there is no such file" is known, the caller's place has already been overwritten - a failed 'do' inside a script would otherwise leave the script that ran it reading from a name it never chose. The test resumes in the outer script's SECOND block, which is the case the whole design turns on and the one an ordinary nesting test would miss. Breaking the re-read, the save, or the limit each fails it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW |
||
|
|
2466d79d9c |
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
|