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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
301716e869
commit
0b6d2be43f
Executable
+60
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""The instruction table, as the assembler has it.
|
||||
|
||||
The monitor needs the same 64 instructions the assembler does, with the same names and the
|
||||
same lengths, and a disassembler that disagreed with the assembler about how long an
|
||||
instruction is would not merely print one thing wrong - it would lose its place and print
|
||||
everything after it wrong too. So the table is generated from assembly.c rather than typed
|
||||
out again, and Tests/docs.sh checks the generated form against what is in the monitor.
|
||||
|
||||
Shapes are what follows the opcode:
|
||||
0 nothing 1 an address 2 one byte 3 a Data Pointer selector
|
||||
4 selector, byte 5 selector, address 6 two selectors
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
|
||||
ADDRESS = {0x10, 0x11, 0x12, 0x13, 0x14, 0x17, 0x1A, 0x1B, 0x1C, 0x1D}
|
||||
ONE_BYTE = {0x18, 0x26, 0x27}
|
||||
TWO_SELECTORS = {0x4A, 0x4B}
|
||||
SELECTOR = {0x15, 0x33, 0x36, 0x40, 0x41, 0x42, 0x43, 0x44,
|
||||
0x45, 0x46, 0x47, 0x48, 0x49, 0x4C, 0x4D}
|
||||
|
||||
|
||||
def shapeOf(opcode):
|
||||
if opcode in TWO_SELECTORS:
|
||||
return 6
|
||||
if opcode == 0x47: # SETD, a selector and then an address
|
||||
return 5
|
||||
if opcode in (0x48, 0x49): # DPUP and DPDN, a selector and then a byte
|
||||
return 4
|
||||
if opcode in SELECTOR:
|
||||
return 3
|
||||
if opcode in ADDRESS:
|
||||
return 1
|
||||
if opcode in ONE_BYTE or (opcode & 0xF0) in (0xD0, 0xE0):
|
||||
return 2
|
||||
return 0
|
||||
|
||||
|
||||
def table(path="Source/Assembler/assembly.c"):
|
||||
source = open(path).read()
|
||||
found = re.findall(r'\{0x([0-9A-Fa-f]{2}),\s*"([A-Z0-9]+)"\}', source)
|
||||
return [(int(code, 16), name) for code, name in found]
|
||||
|
||||
|
||||
def asAssembly(entries):
|
||||
lines = []
|
||||
for opcode, name in entries:
|
||||
padded = (name + " ")[:4]
|
||||
lines.append(' 0x%02X 0d%d "%s"' % (opcode, shapeOf(opcode), padded))
|
||||
return lines
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
entries = table()
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "--count":
|
||||
print(len(entries))
|
||||
else:
|
||||
for line in asAssembly(entries):
|
||||
print(line)
|
||||
Reference in New Issue
Block a user