Commit Graph
10 Commits
Author SHA1 Message Date
AnachronautandClaude Opus 5 20989c3439 M3: programs that bring their own vectors
> load Asm.sbx
    > run Keys.asm
    wrote Keys.sbx: program 558, data 85, labels 52
    > load Keys.sbx
    > run
    keys, by interrupt. q stops.
    ab
    the console has been handed back

The machine assembles a program carrying an interrupt handler, the loader
installs its vector, the console interrupts into it, and the shell takes the
vector back at exit. Byte for byte identical to the C assembler's, and
Tests/native.sh now checks a boot image and four loadable programs on every
run.

WHAT IT TOOK:

  A declaration and an implementation are the SAME ENTRY. services.asm says
  a service is called osPrintString and has number 16; cosmos.asm says
  osPrintString is handled by handlePrintString. The name is met twice and
  the second time fills in the handler, which is what lets one shared file
  serve both the caller and the implementer.

  So the first pass declares and the second implements. That is forced: a
  handler is an address, and no address is known until every label has been
  placed.

  Boot in a loadable program fills the entry field rather than being
  installed - vector zero is where the whole machine starts, and a program
  loaded into a running system has no business saying anything about that.
  A boot image is the one thing that does, so there it is installed like any
  other, behind a "VEC" marker in the SPBT file.

  Device is named by the port, and Device with the five reserved names are
  matched without regard to case, the way mnemonics are: they are part of
  the language rather than names the programmer chose. Devices have no names
  of their own, so they are given one nothing can type.

TWO BUGS, both of a kind worth naming.

The first: "is this a loadable program" was written out as an OR of the two
segment bases in seven places, and the sense wanted is the opposite in most
of them. One of the seven had it backwards and put a version ONE header on a
file carrying vectors, which a loader is right to refuse. It is one flag
now, settled once and tested the same way everywhere.

The second: finding the entry to write a handler into means calling vecFind,
which reads the entry's fields out - including the handler it does not have
yet. An address resolved into VecHandler before the find was overwritten
with zero by the find itself, and the file came out with a vector pointing
at address zero: a slot that looked installed and went nowhere. The
resolved address has a variable of its own now.

Keys.asm and console.asm go on the CosmOS disk, so the whole path can be
watched rather than only tested.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-21 11:50:52 -04:00
AnachronautandClaude Opus 5 04424a8917 Put the application hello.asm on the disk, not the bare metal one
Both are called hello.asm and they are different programs: Programs/hello.asm
is the 1978-style boot image that writes straight to port 0x00 and HALTs,
and CosmOS/Apps/hello.asm is the loadable one that ends with SWI osExit.
The disk had the first, which assembles to a .bin the shell cannot load, so
watching it work meant taking the file off the disk afterwards.

Now it has the second, and the whole thing is two commands:

    > load Asm.sbx
    > run hello.asm
    wrote hello.sbx: program 22, data 14, labels 3
    > load hello.sbx
    > run
    Hello, World!

It writes over the hello.sbx the host tool put there, so what runs is the
machine's own work. Byte for byte identical to the C assembler's, as ever.

strings.asm stays as it is, and now earns its place by being the odd one
out: no #Include and no #Base, so it assembles to a boot image rather than
a loadable program and the difference between the two is visible on one
disk.

Tests/native.sh still builds the bare metal hello.asm, and should: it is the
only thing that checks the SPBT path end to end, including running what came
out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-20 22:55:05 -04:00
AnachronautandClaude Opus 5 c5e4ec3455 M2: the native assembler builds applications
> load Asm.sbx
    > run Say.asm
    wrote Say.sbx: program 46, data 93, labels 7
    > load Say.sbx
    > run built by the machine itself
    it says: built by the machine itself

The machine assembles an application and then runs what it built. Say,
greet and Files all come out byte for byte identical to the C assembler's,
and Tests/native.sh checks all three on every run alongside the boot image
M1 already covered.

WHAT IT TOOK, and it was more than #Include and #Base:

  #Include   The reader is a stack of readers. The current file's whole
             state goes aside - buffer and all, 292 bytes - the new one
             opens, and the end of it pops the old one back. A file goes in
             once; including it twice does nothing, which is what lets two
             libraries depend on a third. The list is forgotten between the
             passes, because the second has to walk the same tree.
  #Base      Cursors start there, so labels hold the addresses the program
             will really have. A program that says where it goes gets the
             SBEX header and a .sbx name; one that says nothing gets SPBT
             and .bin. A program that bases one segment and leaves the
             other unbased with content in it is refused.
  #Reserve   Runs of zeroes, moved over in the first pass and written in
  #Align     the second. How many an #Align comes to depends on where the
             cursor has reached, which is why both passes keep a cursor.
  #Vectors   Names are read and numbered, pinned where the source pins
             them, so SWI osPrintString resolves. Every application needs
             this - a program that calls a service names a vector declared
             in a file it includes.

THE TWO PASSES ARE NOW ONE LOOP, walked twice, with Emitting the only
difference. They have to agree about the length of every token, and the way
they stop agreeing is by being two pieces of code that drifted apart -
which is the exact shape of the bug this assembler found in the C one.
Sharing the body means there is nothing to drift. What is left is checked
anyway: the second pass compares its own totals against the first's and
refuses to write the file if they differ.

THE BUG WORTH RECORDING. The tokenizer holds one character of lookahead,
and at an #Include that character belongs to the file being put aside. It
was carried across and handed back on the way out, which is wrong: a file
runs out in the middle of whatever the tokenizer happens to be doing, so
the character arrives in the middle of a word. `start:` came back as `s`
and then `tart:` - and the result assembled into a perfectly plausible
file. The fix is to undo the read instead, so the character is simply still
there when the file is opened again and the question of when to hand it
back never arises.

Three smaller ones, all old friends: three places took the CONTENTS of a
buffer where they wanted its ADDRESS; vecTakeAuto returned its answer in A,
which a RET puts back; and pass two re-declared every vector because only
the label table was being skipped on the second walk.

sameText moved down into numbers.asm from the label table - four parts want
it now, and a reader test that includes neither labels nor tokens has to
build on its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-20 22:50:39 -04:00
AnachronautandClaude Opus 5 affe9d09ea Put the assembler and some source on the CosmOS disk
'make run-cosmos' now boots a machine with Asm.sbx on the disk and two
source files to point it at, so the thing can be watched working rather
than only tested:

    > load Asm.sbx
    > run hello.asm
    wrote hello.bin: program 17, data 14, labels 2
    > run strings.asm
    wrote strings.bin: program 62, data 88, labels 4

Both come out byte for byte identical to the C assembler's, and both run.
hello.asm and testPrograms/stringKeyword.asm are the two single-file
programs with no #Include, which is what the native assembler handles so
far; strings.asm is the second because it has a subroutine and a label
used before it is defined, so it exercises a forward reference that
hello.asm does not.

The assembler builds from its own directory rather than from Apps/,
because it is not one file. Its pieces are found beside it without being
told, since an include is looked for next to the file that asked for it
before anywhere else; only services.asm needs the include path.

ALSO A CORRECTION. Asm.asm's header, its usage message and the Assembler
Manual all said 'run Asm.sbx hello.asm'. That is the convention we talked
about wanting later, not the one this shell has: load and run are separate
commands, so the whole rest of the run line is the argument and that form
asks for a file called "Asm.sbx hello.asm". All three now say load first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-20 22:20:45 -04:00
AnachronautandClaude Opus 5 ca243895ca Grow the CosmOS disk to 512K, because it has to hold its own source
cosmos.img was 256 blocks and two directory blocks: 64K, sixteen files.
Ample for a disk that held nothing but programs, and not enough for the
thing that comes next. The native assembler reads SOURCE from this disk,
and the CosmOS sources alone are 104,142 bytes against the 65,536 a 256
block disk holds - so the machine could not hold its own source code.

2048 blocks and four directory blocks: 512K and room for 32 files.

Nothing was recorded that had to change, which is worth saying rather
than assuming: dir lists names and does not report free space, so no
golden file names a block count.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-20 21:42:57 -04:00
AnachronautandClaude Opus 5 0b6d2be43f CosmOS: a service interface for the disk and console, and the monitor in the shell
Two changes that arrived together because both live in cosmos.asm.

THE SERVICES. A loaded program that wanted a file had to include the whole
filesystem, carrying two and a half kilobytes of a private copy of code the
system already had running, and then mount a disk that was already mounted.
Five services are added at pinned numbers 20 to 24: osFileRead, osFileSave,
osFileDelete, osFileRename and osPrintNumber.

The sizes fit the registers exactly in both directions. A file that can be
read into Data Memory is under 64K by definition, so its length is sixteen
bits: coming back it is DP3, going out it is A and B together, and neither
direction needs a record in memory whose shape both sides must agree on.

There is deliberately no service to mount a disk. The system mounts one
before its first prompt, and a program mounting it again was only ever a
consequence of owning a second copy of the library, so that call disappears
rather than moving. Apps/Files.asm writes, reads, renames and deletes a file
in 645 bytes and includes nothing but the service names.

THE MONITOR. Previously an application, now part of the shell, because an
application occupies the one region a loaded application is given: a monitor
that was an application could never examine another one, since loading the
thing to be inspected would replace the thing doing the inspecting.

"monitor" turns it on and the prompt becomes "*". It is a mode rather than a
sub-prompt, and it persists: because the mode is a variable the prompt reads
rather than a second loop, and every path back to the prompt goes through one
place including osExit, a program started with "g" that gives the machine back
arrives at the monitor prompt it was started from. Examining a program and
running it therefore do not interrupt each other. "exit" leaves whatever you
are in.

It supersedes dump, and adds disassembly, writing bytes, and jumping to an
address. Its instruction table is generated from the assembler's own list by
Tests/instructiontable.py rather than typed again, and Tests/docs.sh checks
both that the system's copy matches the generator and that the lengths that
table implies are the ones the manual's Bytes column prints. A disassembler
that disagreed about a length would not print one line wrong, it would lose
its place and print everything after it wrong.

Also here: b refuses a bank that is not registered, since asking the
controller for one is refused and a refusal nobody catches stops the machine;
g records the Stack the way run does, without which a program returning
through osExit restored whatever the last run had left; and make cosmos-disk
now depends on the system as well as the image.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 18:19:15 -04:00
Anachronaut e3100b4718 Fixed assembler bug that caused crash on IR array resize. Added line editor app. 2026-08-17 23:26:21 -04:00
Anachronaut 91c9d49d1b CosmOS pre-alpha and launchable application versions of old programs. 2026-08-17 15:31:49 -04:00
Anachronaut 6d1966d500 Interrupt system implemented, some new programs. 2026-08-15 00:44:13 -04:00
Anachronaut 638b68b25c Long standing assembler bugs fixed, new path system. Make compatibility update. 2026-08-14 16:53:28 -04:00