The output image is gone. It was eighteen kilobytes and it is now one block of window, because the file was always produced in order and only ever needed to be written that way. Everything works in FILE OFFSETS now. A cursor is a two byte number counting from the front of the file, and since a block is two hundred and fifty six bytes, the block it lands in is the offset's high byte and the place within that block is its low one - so there is no division anywhere, and ImgWalk, ProgPut and DataPut needed no change but where they start. ONE WINDOW RATHER THAN THREE. The plan said three: one per segment, and a third for the block where the program ends and the data begins, which belongs to both. Fetching a block back instead makes all of that one case. The header is patched after every byte is out, the boundary block is written by both cursors, and both are simply revisits - a revisit is what fetching handles. osFileFetch is the service that allows it, and is the read side of the write. A run of bytes in one segment costs nothing extra; a switch between segments costs two block operations, and a source file has a few dozen switches and several thousand bytes. Two bugs, both a pointer meaning two things: putAt took the cursor to advance in DP2 and then wanted DP2 for the window's address. A call puts DP2 back the way it was AT THE CALL, so the step at the end moved whatever the last call had left there - the window walked off across memory while the cursor stood still. It goes in memory now, like the block did in S1, and for the same reason. The size the file is created at could not be right. How many vectors are actually installed is not known until the second pass has resolved their handlers, and by then the file must already exist to be written into - so Keys, which brings one vector, came out four bytes short. Teaching the first pass to count them meant teaching it about devices, and about a Boot line in a loadable program not being installed at all, which is two ways to disagree with the second pass about what a file contains. So osFileDone is told the size instead. A writer asks for as much as the file could possibly come to - the whole of it plus four bytes for every vector DECLARED, which no file can exceed - and says what it really came to at the end. The blocks it did not use go back to the free count. Asking for too much costs a moment; asking for too little writes off the end of a file. That is a better service for it, not a workaround. A writer that cannot know its size until the last byte is the ordinary case, and it is exactly the case this whole rung exists for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2473 lines
48 KiB
NASM
2473 lines
48 KiB
NASM
; The SplitBit assembler, running on SplitBit.
|
|
;
|
|
; > load Asm.sbx
|
|
; > run hello.asm
|
|
;
|
|
; Loading and running are separate commands in this shell, so the file to assemble is the
|
|
; argument to run rather than a second name after the program's.
|
|
;
|
|
; Reads assembly source off the disk and writes a boot image or a loadable program back to it,
|
|
; with no host involved
|
|
; anywhere. The output has to be byte for byte what the C assembler produces from the same
|
|
; source, which is the only honest test of it and the one the suite runs.
|
|
;
|
|
; ---- Two passes over a file that is never held ----
|
|
;
|
|
; The C assembler reads every token of every file into one array and works on that. It
|
|
; cannot be done that way here and never could: cosmos.asm alone is 56,047 bytes of source
|
|
; against 64K of Data Memory, and the token array for it would be several times that. So
|
|
; the source is streamed through a 256 byte window, twice, and the only thing kept between
|
|
; the passes is the label table.
|
|
;
|
|
; TWO PASSES ARE ENOUGH BECAUSE EVERY LENGTH IS KNOWN WITHOUT RESOLVING ANYTHING. How many
|
|
; bytes a token comes to falls out of what the token is - an instruction's from its shape,
|
|
; a value's is one, a string's is its characters and a zero - and never from the value of
|
|
; anything named. So the first pass can work out exactly where every label lands, and the
|
|
; second never needs a fixup list or a second look. A forward reference stops being a
|
|
; problem and becomes the reason there are two passes at all.
|
|
;
|
|
; ---- What this one does not do yet ----
|
|
;
|
|
; #Include, #Base, #Align, #Reserve and #Vectors are refused by name rather than ignored.
|
|
; An assembler that quietly skipped a directive would produce a file that looked right and
|
|
; was the wrong length, which is the worst thing it could do.
|
|
;
|
|
; Written by Anachronaut
|
|
|
|
#Include services.asm
|
|
|
|
#Program
|
|
|
|
#Base 0x4000
|
|
|
|
start:
|
|
SETD.0 Argument
|
|
INIB 0d23
|
|
SWI osArgument
|
|
SETD.0 Argument
|
|
LDA.0
|
|
BRA sayUsage
|
|
|
|
SETD.0 Argument
|
|
CALL srcOpen
|
|
BNQ noSource
|
|
|
|
CALL passOne
|
|
BNQ stopped
|
|
CALL settleFormat
|
|
CALL deriveName ; After the first pass: what it is called depends on whether a
|
|
; #Base turned up, and that is not known until then.
|
|
|
|
CALL layOutImage
|
|
BNQ stopped
|
|
CALL passTwo
|
|
BNQ stopped
|
|
CALL writeVectors
|
|
CALL writeImage
|
|
BNQ stopped
|
|
|
|
CALL report
|
|
SWI osExit
|
|
|
|
stopped:
|
|
SETD.0 StoppedText
|
|
SWI osPrintString
|
|
SWI osExit
|
|
|
|
sayUsage:
|
|
SETD.0 UsageText
|
|
SWI osPrintString
|
|
SWI osExit
|
|
|
|
noSource:
|
|
SETD.0 NoSourceText
|
|
SWI osPrintString
|
|
SETD.0 Argument
|
|
SWI osPrintString
|
|
SETD.0 NewLine
|
|
SWI osPrintString
|
|
SWI osExit
|
|
|
|
; ---- The passes ----
|
|
;
|
|
; ONE LOOP, WALKED TWICE. The first time it works out how long everything is and where
|
|
; every label lands; the second time it does all of that again and writes the bytes as
|
|
; well. Emitting is the only difference between them.
|
|
;
|
|
; That is deliberate. The two passes have to agree about the length of every single token,
|
|
; and the way they stop agreeing is by being two pieces of code that drifted apart - which
|
|
; is exactly the 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 at the end of the second pass.
|
|
|
|
passOne:
|
|
CALL labReset
|
|
CALL vecReset
|
|
RSTA
|
|
SETD.0 Emitting
|
|
STA.0
|
|
CALL runPass
|
|
BNQ passFailed
|
|
CALL checkSegmentBases
|
|
RET
|
|
|
|
passTwo:
|
|
INIA 0d1
|
|
SETD.0 Emitting
|
|
STA.0
|
|
CALL runPass
|
|
BNQ passFailed
|
|
|
|
; The two passes must have counted the same, and if they did not, everything after the
|
|
; first disagreement is in the wrong place. Better to say so than to write the file.
|
|
SETD.0 ProgAt
|
|
SETD.2 ProgBase
|
|
CALL numTake
|
|
SETD.0 ProgAt
|
|
SETD.2 ImgProgLen
|
|
CALL numCompare
|
|
BNQ passesDisagree
|
|
SETD.0 DataAt
|
|
SETD.2 DataBase
|
|
CALL numTake
|
|
SETD.0 DataAt
|
|
SETD.2 ImgDataLen
|
|
CALL numCompare
|
|
BNQ passesDisagree
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
passesDisagree:
|
|
SETD.0 DisagreeText
|
|
SWI osPrintString
|
|
passFailed:
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
runPass:
|
|
CALL srcRestart
|
|
BNQ passFailed
|
|
CALL beginPass
|
|
|
|
passLoop:
|
|
CALL tokNext
|
|
BNQ passDone
|
|
CALL clsToken
|
|
BNQ passFailed
|
|
|
|
; ---- The Vector Segment, where nothing becomes a byte ----
|
|
SETD.0 Status
|
|
LDA.0
|
|
INIB 0d3
|
|
XOR
|
|
BNQ passNotVectors
|
|
SETD.0 ClsType
|
|
LDA.0
|
|
BNA passVectorLine ; A keyword here is still a keyword.
|
|
|
|
passNotVectors:
|
|
; ---- A name after SWI is a vector, not an address ----
|
|
;
|
|
; One byte instead of two, and it is settled by what the name FOLLOWS rather than by
|
|
; anything about the name. That matters: the Vector Segment may not have been read yet,
|
|
; since it can live in a file included further down.
|
|
SETD.0 ClsType
|
|
LDA.0
|
|
INIB 0d5
|
|
XOR
|
|
BNQ passNotVectorName
|
|
SETD.0 AfterSwi
|
|
LDA.0
|
|
BRA passNotVectorName
|
|
INIA 0d6
|
|
SETD.0 ClsType
|
|
STA.0
|
|
INIA 0d1
|
|
CALL clsSetLength
|
|
|
|
passNotVectorName:
|
|
; Whether the NEXT name is one of those depends on this token, so it is written down
|
|
; before this one is dealt with.
|
|
RSTA
|
|
SETD.0 AfterSwi
|
|
STA.0
|
|
SETD.0 ClsType
|
|
LDA.0
|
|
INIB 0d1
|
|
XOR
|
|
BNQ passNotSwi
|
|
SETD.0 ClsOpcode
|
|
LDA.0
|
|
INIB 0x18
|
|
XOR
|
|
BNQ passNotSwi
|
|
INIA 0d1
|
|
SETD.0 AfterSwi
|
|
STA.0
|
|
|
|
passNotSwi:
|
|
SETD.0 ClsType
|
|
LDA.0
|
|
BNA passNotKeyword
|
|
CALL doKeyword
|
|
BNQ passFailed
|
|
BRI passLoop ; A keyword is no bytes, so there is nothing to move over.
|
|
|
|
passNotKeyword:
|
|
SETD.0 ClsType
|
|
LDA.0
|
|
INIB 0d4
|
|
XOR
|
|
BNQ passNotDefinition
|
|
CALL doDefinition
|
|
BNQ passFailed
|
|
BRI passLoop ; A definition names a place; it does not take one up.
|
|
|
|
passNotDefinition:
|
|
CALL checkPlacement
|
|
BNQ passFailed
|
|
CALL markSegmentUsed
|
|
SETD.0 Emitting
|
|
LDA.0
|
|
BRA passMove
|
|
CALL emitToken
|
|
BNQ passFailed
|
|
passMove:
|
|
SETD.2 ClsLength
|
|
CALL moveCursor
|
|
BRI passLoop
|
|
|
|
passVectorLine:
|
|
CALL doVectorLine
|
|
BNQ passFailed
|
|
BRI passLoop
|
|
|
|
passDone:
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; A label definition. The colon is not part of the name: writing a zero over it means a
|
|
; definition and a use of the same name compare equal without either side knowing which it
|
|
; is looking at.
|
|
doDefinition:
|
|
CALL dropColon
|
|
SETD.0 Status
|
|
LDA.0
|
|
BRA labelNowhere
|
|
INIB 0d3
|
|
XOR
|
|
BRQ labelNowhere ; The Vector Segment has no addresses to name.
|
|
|
|
SETD.0 Emitting
|
|
LDA.0
|
|
BNA definitionDone ; The first pass took its address; the second only re-reads it.
|
|
|
|
SETD.0 ProgAt
|
|
SETD.2 Status
|
|
LDA.2
|
|
INIB 0d1
|
|
XOR
|
|
BRQ definitionHere
|
|
SETD.0 DataAt
|
|
definitionHere:
|
|
LDA.0
|
|
INCD.0
|
|
LDB.0
|
|
SETD.0 TokText
|
|
CALL labAdd
|
|
RET
|
|
|
|
definitionDone:
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
labelNowhere:
|
|
SETD.0 LabelNowhereText
|
|
CALL clsComplain
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; ---- A line of the Vector Segment ----
|
|
;
|
|
; Four shapes, and which one it is cannot be known without looking ahead:
|
|
;
|
|
; name declared, and the assembler gives it a number
|
|
; name 0dNN declared with a number both sides have agreed
|
|
; name handler this program implements it
|
|
; name 0dNN handler both at once
|
|
; Device 0xNN handler named by the port, because that is what decides it
|
|
;
|
|
; A LINE is what tells them apart. Two names on one line are a name and its handler; two
|
|
; names on two lines are two declarations. So the next token is read and handed back if it
|
|
; turns out to belong to the following entry.
|
|
;
|
|
; THE FIRST PASS DECLARES AND THE SECOND IMPLEMENTS. Handlers are addresses and no address
|
|
; is known until every label has been placed, so the second pass is the earliest a handler
|
|
; can be resolved - and by then the names are all in the table waiting for one.
|
|
doVectorLine:
|
|
RSTA
|
|
SETD.0 VecPinnedGiven
|
|
STA.0
|
|
SETD.0 VecHandlerHere
|
|
STA.0
|
|
SETD.0 VecIsDevice
|
|
STA.0
|
|
|
|
SETD.0 TokText
|
|
SETD.1 WordDevice
|
|
CALL sameFolded
|
|
BNQ vectorNamed
|
|
INIA 0d1
|
|
SETD.0 VecIsDevice
|
|
STA.0
|
|
|
|
vectorNamed:
|
|
SETD.0 TokText
|
|
SETD.1 VecName
|
|
CALL srcKeepName
|
|
SETD.0 VecLineWas
|
|
SETD.2 TokLine
|
|
CALL numSet
|
|
|
|
; ---- Whatever else is on this line ----
|
|
CALL vectorNextOnLine
|
|
BNQ vectorLineEnds
|
|
|
|
SETD.0 ClsType
|
|
LDA.0
|
|
INIB 0d2
|
|
XOR
|
|
BNQ vectorMaybeHandler
|
|
|
|
; A number. Between a name and any handler it PINS the vector; after Device it says
|
|
; which port. Nothing else can appear there, because a label may not begin with a digit.
|
|
INIA 0d1
|
|
SETD.0 VecPinnedGiven
|
|
STA.0
|
|
SETD.0 VecPinned
|
|
SETD.2 ClsValue
|
|
LDA.2
|
|
STA.0
|
|
CALL vectorNextOnLine
|
|
BNQ vectorLineEnds
|
|
|
|
vectorMaybeHandler:
|
|
; A name here is the handler. Its address is wanted, and only the second pass has one.
|
|
SETD.0 ClsType
|
|
LDA.0
|
|
INIB 0d5
|
|
XOR
|
|
BNQ vectorOddToken
|
|
INIA 0d1
|
|
SETD.0 VecHandlerHere
|
|
STA.0
|
|
SETD.0 TokText
|
|
SETD.1 VecHandlerName
|
|
CALL srcKeepName
|
|
|
|
; Anything after the handler is a line that says too much.
|
|
CALL vectorNextOnLine
|
|
BRQ vectorTooMuch
|
|
|
|
vectorLineEnds:
|
|
SETD.0 Emitting
|
|
LDA.0
|
|
BNA vectorImplement
|
|
BRI vectorDeclareIt
|
|
|
|
; ---- The second pass: fill in the handler ----
|
|
vectorImplement:
|
|
SETD.0 VecHandlerHere
|
|
LDA.0
|
|
BRA vectorLineDone ; Nothing to implement, so nothing to do.
|
|
|
|
SETD.0 VecHandlerName
|
|
CALL labFind
|
|
BNQ vectorNoHandler
|
|
SETD.0 VecPutHandler
|
|
SETD.2 LabAddress
|
|
CALL numSet
|
|
|
|
SETD.0 VecIsDevice
|
|
LDA.0
|
|
BNA vectorImplementDevice
|
|
|
|
SETD.0 VecName
|
|
CALL vecFind
|
|
BNQ vectorLost
|
|
CALL vecWriteHandler
|
|
BRI vectorLineDone
|
|
|
|
vectorImplementDevice:
|
|
; A device brings no name, so there is nothing to look up: the entry is made now, in the
|
|
; place the line sits. Nothing can refer to it, which is why it needs no name of its own.
|
|
SETD.0 VecPinned
|
|
LDA.0
|
|
SETD.0 VecPutNumber
|
|
STA.0
|
|
INIA 0d1
|
|
SETD.0 VecPutBase
|
|
STA.0
|
|
SETD.0 DeviceName
|
|
SETD.2 VecPutNumber
|
|
LDA.2
|
|
CALL vecDeclare
|
|
BNQ vectorFailed
|
|
SETD.0 DeviceName
|
|
CALL vecFind
|
|
BNQ vectorLost
|
|
CALL vecWriteHandler
|
|
CALL vecFreshDeviceName
|
|
BRI vectorLineDone
|
|
|
|
; ---- The first pass: give it a number ----
|
|
vectorDeclareIt:
|
|
SETD.0 VecIsDevice
|
|
LDA.0
|
|
BNA vectorDeviceCheck
|
|
|
|
SETD.0 VecName
|
|
CALL vecFind
|
|
BRQ vectorAlready
|
|
|
|
; A reserved name is where the machine looks rather than where a program says to look,
|
|
; so its number is not anybody's to choose.
|
|
CALL vectorReserved
|
|
BNQ vectorNotReserved
|
|
SETD.0 VecPinnedGiven
|
|
LDA.0
|
|
BNA vectorFixedNumber
|
|
BRI vectorSetNumber
|
|
|
|
vectorNotReserved:
|
|
SETD.0 VecPinnedGiven
|
|
LDA.0
|
|
BRA vectorAutoNumber
|
|
|
|
; A pinned number comes from the range set aside for what two separately assembled
|
|
; programs have to agree about. Below it belongs to the machine and above it is handed
|
|
; out by the assembler, so neither can be asked for.
|
|
SETD.0 VecPinned
|
|
LDA.0
|
|
INIB 0d16
|
|
CCF
|
|
SUB
|
|
BRC vectorBadNumber
|
|
SETD.0 VecPinned
|
|
LDA.0
|
|
INIB 0d64
|
|
CCF
|
|
SUB
|
|
BNC vectorBadNumber
|
|
BRI vectorSetNumber
|
|
|
|
vectorAutoNumber:
|
|
CALL vecTakeAuto
|
|
SETD.0 VecPutNumber
|
|
LDA.0
|
|
SETD.0 VecPinned
|
|
STA.0
|
|
|
|
vectorSetNumber:
|
|
SETD.0 VecPinned
|
|
LDA.0
|
|
SETD.0 VecPutNumber
|
|
STA.0
|
|
RSTA
|
|
SETD.0 VecPutBase
|
|
STA.0
|
|
SETD.0 VecName
|
|
SETD.2 VecPutNumber
|
|
LDA.2
|
|
CALL vecDeclare
|
|
BNQ vectorFailed
|
|
BRI vectorLineDone
|
|
|
|
vectorAlready:
|
|
; Met before. A handler now is somebody implementing what was declared earlier; nothing
|
|
; but a handler means the name was declared twice.
|
|
SETD.0 VecHandlerHere
|
|
LDA.0
|
|
BRA vectorTwice
|
|
SETD.0 VecPinnedGiven
|
|
LDA.0
|
|
BRA vectorLineDone
|
|
SETD.0 VecPinned
|
|
LDA.0
|
|
SETD.2 VecNumber
|
|
LDB.2
|
|
XOR
|
|
BNQ vectorDisagrees ; The two sides name different vectors by one name.
|
|
BRI vectorLineDone
|
|
|
|
vectorDeviceCheck:
|
|
; Device says which port as a number, and then where to go.
|
|
SETD.0 VecPinnedGiven
|
|
LDA.0
|
|
BRA vectorDeviceBare
|
|
SETD.0 VecHandlerHere
|
|
LDA.0
|
|
BRA vectorDeviceBare
|
|
|
|
vectorLineDone:
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; The next token of this line, if there is one. Q is zero if there was, and it has been
|
|
; classified; otherwise it has been handed back for the next entry to have.
|
|
vectorNextOnLine:
|
|
CALL tokNext
|
|
BNQ vectorNextNone
|
|
SETD.0 TokLine
|
|
SETD.2 VecLineWas
|
|
CALL numCompare
|
|
BNQ vectorNextOther
|
|
CALL clsToken
|
|
BNQ vectorNextNone
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
vectorNextOther:
|
|
CALL tokBack
|
|
vectorNextNone:
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; Q is zero if VecName is one of the vectors the machine itself uses, and then VecPinned
|
|
; is the number it must have. These are matched without regard to case, the way mnemonics
|
|
; are: they are part of the language rather than names somebody chose.
|
|
vectorReserved:
|
|
RSTA
|
|
SETD.0 ReservedLeft
|
|
STA.0
|
|
SETD.0 ReservedNames
|
|
SETD.1 ReservedWalk
|
|
STD.0.1
|
|
|
|
reservedLoop:
|
|
SETD.0 ReservedLeft
|
|
LDA.0
|
|
SETD.2 ReservedCount
|
|
LDB.2
|
|
CCF
|
|
SUB
|
|
BRQ reservedNo
|
|
SETD.1 ReservedWalk
|
|
LDD.0.1
|
|
SETD.1 VecName
|
|
CALL sameFolded
|
|
BRQ reservedYes
|
|
INIA 0d16
|
|
SETD.0 ReservedWalk
|
|
CALL numAddByte
|
|
SETD.0 ReservedLeft
|
|
LDA.0
|
|
INCA
|
|
STA.0
|
|
BRI reservedLoop
|
|
|
|
reservedYes:
|
|
SETD.1 ReservedWalk
|
|
LDD.0.1
|
|
DPUP.0 0d15
|
|
LDA.0
|
|
SETD.0 VecPinned
|
|
STA.0
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
reservedNo:
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; Devices have no names of their own, so they are given one nothing can type: a space
|
|
; followed by a counter. It keeps them apart in a table that refuses a repeated name.
|
|
vecFreshDeviceName:
|
|
SETD.0 DeviceName
|
|
INCD.0
|
|
LDA.0
|
|
INCA
|
|
STA.0
|
|
RET
|
|
|
|
vectorFixedNumber:
|
|
SETD.0 FixedNumberText
|
|
CALL clsComplain
|
|
BRI vectorFailed
|
|
vectorBadNumber:
|
|
SETD.0 BadNumberText
|
|
CALL clsComplain
|
|
BRI vectorFailed
|
|
vectorTwice:
|
|
SETD.0 VecTwiceText
|
|
CALL clsComplain
|
|
BRI vectorFailed
|
|
vectorDisagrees:
|
|
SETD.0 DisagreeNumberText
|
|
CALL clsComplain
|
|
BRI vectorFailed
|
|
vectorDeviceBare:
|
|
SETD.0 DeviceBareText
|
|
CALL clsComplain
|
|
BRI vectorFailed
|
|
vectorNoHandler:
|
|
SETD.0 NoHandlerText
|
|
CALL clsComplain
|
|
BRI vectorFailed
|
|
vectorLost:
|
|
SETD.0 LostVectorText
|
|
CALL clsComplain
|
|
BRI vectorFailed
|
|
vectorTooMuch:
|
|
SETD.0 TooMuchText
|
|
CALL clsComplain
|
|
BRI vectorFailed
|
|
vectorOddToken:
|
|
SETD.0 VectorOddText
|
|
CALL clsComplain
|
|
vectorFailed:
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; ---- The bytes of one token ----
|
|
|
|
emitToken:
|
|
SETD.0 ClsType
|
|
LDA.0
|
|
INIB 0d1
|
|
XOR
|
|
BRQ emitInstruction
|
|
SETD.0 ClsType
|
|
LDA.0
|
|
INIB 0d2
|
|
XOR
|
|
BRQ emitValue
|
|
SETD.0 ClsType
|
|
LDA.0
|
|
INIB 0d3
|
|
XOR
|
|
BRQ emitString
|
|
SETD.0 ClsType
|
|
LDA.0
|
|
INIB 0d6
|
|
XOR
|
|
BRQ emitVector
|
|
BRI emitReference
|
|
|
|
emitInstruction:
|
|
SETD.0 ClsOpcode
|
|
LDA.0
|
|
CALL emitByte
|
|
|
|
; The selectors follow the opcode, and they go out whether or not they were written:
|
|
; leaving one off means Data Pointer 0 rather than no pointer at all.
|
|
RSTA
|
|
SETD.0 EmitLeft
|
|
STA.0
|
|
emitSelectorLoop:
|
|
SETD.0 EmitLeft
|
|
LDA.0
|
|
SETD.2 ClsWanted
|
|
LDB.2
|
|
CCF
|
|
SUB
|
|
BRQ emitDone
|
|
SETD.0 ClsSelectorValue
|
|
SETD.2 EmitLeft
|
|
LDA.2
|
|
CALL byteAt
|
|
SETD.0 ClsByte
|
|
LDA.0
|
|
CALL emitByte
|
|
SETD.0 EmitLeft
|
|
LDA.0
|
|
INCA
|
|
STA.0
|
|
BRI emitSelectorLoop
|
|
|
|
emitValue:
|
|
SETD.0 ClsValue
|
|
LDA.0
|
|
CALL emitByte
|
|
BRI emitDone
|
|
|
|
emitString:
|
|
SETD.0 TokText
|
|
SETD.1 EmitWalk
|
|
STD.0.1
|
|
emitStringLoop:
|
|
SETD.1 EmitWalk
|
|
LDD.0.1
|
|
LDA.0
|
|
CALL emitByte
|
|
SETD.0 EmitWalk
|
|
CALL numStep
|
|
SETD.1 EmitWalk
|
|
LDD.0.1
|
|
DECD.0
|
|
LDA.0
|
|
BNA emitStringLoop ; The zero goes out with the rest and then stops the loop.
|
|
BRI emitDone
|
|
|
|
emitVector:
|
|
SETD.0 TokText
|
|
CALL vecFind
|
|
BNQ emitNoVector
|
|
SETD.0 VecNumber
|
|
LDA.0
|
|
CALL emitByte
|
|
BRI emitDone
|
|
|
|
emitReference:
|
|
SETD.0 TokText
|
|
CALL labFind
|
|
BNQ emitNoLabel
|
|
SETD.0 LabAddress
|
|
LDA.0
|
|
CALL emitByte
|
|
SETD.0 LabAddress
|
|
INCD.0
|
|
LDA.0
|
|
CALL emitByte
|
|
|
|
emitDone:
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
emitNoLabel:
|
|
SETD.0 UnknownText
|
|
CALL clsComplain
|
|
BRI emitStopped
|
|
emitNoVector:
|
|
SETD.0 UnknownVectorText
|
|
CALL clsComplain
|
|
emitStopped:
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; ---- What both passes have in common ----
|
|
|
|
beginPass:
|
|
RSTA
|
|
SETD.0 Status
|
|
STA.0
|
|
SETD.0 AfterSwi
|
|
STA.0
|
|
SETD.0 ProgUsed
|
|
STA.0
|
|
SETD.0 DataUsed
|
|
STA.0
|
|
SETD.0 ProgBased
|
|
STA.0
|
|
SETD.0 DataBased
|
|
STA.0
|
|
SETD.0 ProgBase
|
|
CALL numZero
|
|
SETD.0 DataBase
|
|
CALL numZero
|
|
SETD.0 ProgAt
|
|
CALL numZero
|
|
SETD.0 DataAt
|
|
CALL numZero
|
|
RET
|
|
|
|
; Moves the cursor of whichever segment is open along by the two byte number at DP2.
|
|
moveCursor:
|
|
SETD.0 ProgAt
|
|
SETD.1 Status
|
|
LDA.1
|
|
INIB 0d1
|
|
XOR
|
|
BRQ moveInProgram
|
|
SETD.0 DataAt
|
|
moveInProgram:
|
|
CALL numAdd
|
|
RET
|
|
|
|
markSegmentUsed:
|
|
SETD.0 Status
|
|
LDA.0
|
|
INIB 0d1
|
|
XOR
|
|
BRQ markInProgram
|
|
SETD.0 DataUsed
|
|
BRI markSet
|
|
markInProgram:
|
|
SETD.0 ProgUsed
|
|
markSet:
|
|
INIA 0d1
|
|
STA.0
|
|
RET
|
|
|
|
; Is this token allowed where it is? The rules are the C assembler's, and each of them
|
|
; exists because that mistake has a way of going wrong quietly.
|
|
checkPlacement:
|
|
SETD.0 ClsType
|
|
LDA.0
|
|
INIB 0d1
|
|
XOR
|
|
BRQ placeInstruction
|
|
SETD.0 ClsType
|
|
LDA.0
|
|
INIB 0d3
|
|
XOR
|
|
BRQ placeString
|
|
; A value or a name, which needs somewhere to go but does not care which.
|
|
SETD.0 Status
|
|
LDA.0
|
|
BRA placeNowhere
|
|
INIB 0d3
|
|
XOR
|
|
BRQ placeNowhere
|
|
BRI placeYes
|
|
|
|
placeInstruction:
|
|
SETD.0 Status
|
|
LDA.0
|
|
INIB 0d1
|
|
XOR
|
|
BNQ placeNotProgram
|
|
BRI placeYes
|
|
|
|
placeString:
|
|
; A string in Program Memory could not be read by the program holding it: instructions
|
|
; reach Data Memory only. It would assemble and then be unreachable.
|
|
SETD.0 Status
|
|
LDA.0
|
|
INIB 0d1
|
|
XOR
|
|
BRQ placeStringInProgram
|
|
SETD.0 Status
|
|
LDA.0
|
|
INIB 0d2
|
|
XOR
|
|
BNQ placeNowhere
|
|
|
|
placeYes:
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
placeNowhere:
|
|
SETD.0 NowhereText
|
|
CALL clsComplain
|
|
BRI placeNo
|
|
placeNotProgram:
|
|
SETD.0 NotProgramText
|
|
CALL clsComplain
|
|
BRI placeNo
|
|
placeStringInProgram:
|
|
SETD.0 StringInProgramText
|
|
CALL clsComplain
|
|
placeNo:
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; A program that says where one of its segments goes and leaves the other one where it
|
|
; falls is not saying anything about the second - it is forgetting. The segment lands at
|
|
; zero, on top of whatever is there, and the program runs right up until it reads it.
|
|
checkSegmentBases:
|
|
SETD.0 ProgBased
|
|
LDA.0
|
|
SETD.2 DataBased
|
|
LDB.2
|
|
XOR
|
|
BRQ basesAgree
|
|
|
|
SETD.0 ProgBased
|
|
LDA.0
|
|
BNA basesDataMissing
|
|
SETD.0 ProgUsed
|
|
LDA.0
|
|
BNA basesMismatch
|
|
BRI basesAgree
|
|
|
|
basesDataMissing:
|
|
SETD.0 DataUsed
|
|
LDA.0
|
|
BNA basesMismatch
|
|
|
|
basesAgree:
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
basesMismatch:
|
|
SETD.0 BasesText
|
|
SWI osPrintString
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; ---- The directives ----
|
|
;
|
|
; Which one it is, and what that does. #Vectors is understood as far as declaring names;
|
|
; a program that IMPLEMENTS a vector needs a Vector Segment in the output file, which is
|
|
; not built yet and is refused rather than skipped. An assembler that quietly ignored a
|
|
; directive would produce a file that looked right and was the wrong length.
|
|
doKeyword:
|
|
SETD.0 TokText
|
|
SETD.1 WordProgram
|
|
CALL sameText
|
|
BRQ keywordProgram
|
|
SETD.0 TokText
|
|
SETD.1 WordData
|
|
CALL sameText
|
|
BRQ keywordData
|
|
SETD.0 TokText
|
|
SETD.1 WordVectors
|
|
CALL sameText
|
|
BRQ keywordVectors
|
|
SETD.0 TokText
|
|
SETD.1 WordBase
|
|
CALL sameText
|
|
BRQ keywordBase
|
|
SETD.0 TokText
|
|
SETD.1 WordInclude
|
|
CALL sameText
|
|
BRQ keywordInclude
|
|
SETD.0 TokText
|
|
SETD.1 WordReserve
|
|
CALL sameText
|
|
BRQ keywordReserve
|
|
SETD.0 TokText
|
|
SETD.1 WordAlign
|
|
CALL sameText
|
|
BRQ keywordAlign
|
|
SETD.0 NotYetText
|
|
CALL clsComplain
|
|
BRI keywordNo
|
|
|
|
keywordProgram:
|
|
INIA 0d1
|
|
BRI keywordSet
|
|
keywordData:
|
|
INIA 0d2
|
|
BRI keywordSet
|
|
keywordVectors:
|
|
INIA 0d3
|
|
keywordSet:
|
|
SETD.0 Status
|
|
STA.0
|
|
BRI keywordYes
|
|
|
|
; #Base says where this segment is loaded, which is what makes a program a loadable one
|
|
; rather than a boot image. Labels then hold the addresses they will really have, because
|
|
; nothing relocates anything: this is right at assembly time or not at all.
|
|
keywordBase:
|
|
SETD.0 Status
|
|
LDA.0
|
|
BRA baseNowhere
|
|
INIB 0d3
|
|
XOR
|
|
BRQ baseNowhere ; The Vector Segment has no cursor to be the base of.
|
|
|
|
CALL takeNumber
|
|
BNQ keywordNo
|
|
|
|
; A base says where the WHOLE segment begins, so it comes before anything is in it.
|
|
CALL segmentIsUsed
|
|
BRQ baseTooLate
|
|
|
|
SETD.0 Status
|
|
LDA.0
|
|
INIB 0d1
|
|
XOR
|
|
BRQ baseProgram
|
|
SETD.0 DataBase
|
|
SETD.2 ClsWord
|
|
CALL numSet
|
|
SETD.0 DataAt
|
|
SETD.2 ClsWord
|
|
CALL numSet
|
|
INIA 0d1
|
|
SETD.0 DataBased
|
|
STA.0
|
|
BRI keywordYes
|
|
|
|
baseProgram:
|
|
SETD.0 ProgBase
|
|
SETD.2 ClsWord
|
|
CALL numSet
|
|
SETD.0 ProgAt
|
|
SETD.2 ClsWord
|
|
CALL numSet
|
|
INIA 0d1
|
|
SETD.0 ProgBased
|
|
STA.0
|
|
BRI keywordYes
|
|
|
|
; #Include: the reader takes over. It puts this file aside, opens that one, and pops back
|
|
; when it ends, so nothing else in either pass knows an include happened.
|
|
keywordInclude:
|
|
CALL tokNext
|
|
BNQ includeBare
|
|
CALL tokUnread ; The character in hand belongs to the file being put aside.
|
|
SETD.0 TokText
|
|
CALL srcInclude
|
|
BNQ keywordNo
|
|
BRI keywordYes
|
|
|
|
; #Reserve: a run of zero bytes, so a label can stand for a region rather than only its
|
|
; first byte.
|
|
keywordReserve:
|
|
SETD.0 Status
|
|
LDA.0
|
|
BRA reserveNowhere
|
|
INIB 0d3
|
|
XOR
|
|
BRQ reserveNowhere
|
|
CALL takeNumber
|
|
BNQ keywordNo
|
|
SETD.0 RunLength
|
|
SETD.2 ClsWord
|
|
CALL numSet
|
|
CALL layDownRun
|
|
BRI keywordYes
|
|
|
|
; #Align: as many zeroes as it takes to reach the next multiple of what follows. HOW MANY
|
|
; THAT IS DEPENDS ON WHERE THE CURSOR HAS REACHED, so unlike everything else it cannot be
|
|
; worked out from the token alone - which is one reason both passes keep a cursor rather
|
|
; than the second one keeping only a write pointer.
|
|
keywordAlign:
|
|
SETD.0 Status
|
|
LDA.0
|
|
BRA alignNowhere
|
|
INIB 0d3
|
|
XOR
|
|
BRQ alignNowhere
|
|
CALL takeNumber
|
|
BNQ keywordNo
|
|
CALL howFarToAlign
|
|
BNQ keywordNo
|
|
CALL layDownRun
|
|
|
|
keywordYes:
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
baseNowhere:
|
|
SETD.0 BaseNowhereText
|
|
CALL clsComplain
|
|
BRI keywordNo
|
|
baseTooLate:
|
|
SETD.0 BaseLateText
|
|
CALL clsComplain
|
|
BRI keywordNo
|
|
includeBare:
|
|
SETD.0 IncludeBareText
|
|
SWI osPrintString
|
|
BRI keywordNo
|
|
reserveNowhere:
|
|
SETD.0 ReserveNowhereText
|
|
CALL clsComplain
|
|
BRI keywordNo
|
|
alignNowhere:
|
|
SETD.0 AlignNowhereText
|
|
CALL clsComplain
|
|
keywordNo:
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; The number after a directive, into ClsWord. Sixteen bits, because an address and a count
|
|
; are both wider than the one byte a literal inside a segment may be.
|
|
takeNumber:
|
|
CALL tokNext
|
|
BNQ takeNumberBare
|
|
SETD.0 TokString
|
|
LDA.0
|
|
BNA takeNumberBad
|
|
SETD.0 TokText
|
|
LDA.0
|
|
INIB 0x30
|
|
XOR
|
|
BNQ takeNumberBad
|
|
CALL clsWord
|
|
RET
|
|
|
|
takeNumberBare:
|
|
SETD.0 NumberBareText
|
|
SWI osPrintString
|
|
BRI takeNumberNo
|
|
takeNumberBad:
|
|
SETD.0 NumberBadText
|
|
CALL clsComplain
|
|
takeNumberNo:
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; Q is zero if the open segment already has something in it.
|
|
segmentIsUsed:
|
|
SETD.0 Status
|
|
LDA.0
|
|
INIB 0d1
|
|
XOR
|
|
BRQ segmentUsedProgram
|
|
SETD.0 DataUsed
|
|
BRI segmentUsedTest
|
|
segmentUsedProgram:
|
|
SETD.0 ProgUsed
|
|
segmentUsedTest:
|
|
LDA.0
|
|
BNA segmentUsedYes
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
segmentUsedYes:
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; How many zeroes an #Align of ClsWord comes to from here, into RunLength.
|
|
;
|
|
; The remainder by repeated subtraction, since there is no divide. Alignments are small in
|
|
; practice and a segment is at most 64K, so this is bounded and rare.
|
|
howFarToAlign:
|
|
SETD.0 ClsWord
|
|
LDA.0
|
|
INCD.0
|
|
LDB.0
|
|
OR
|
|
BRQ alignByZero ; A multiple of nothing is not a thing to ask for.
|
|
|
|
SETD.0 AlignLeft
|
|
SETD.2 ProgAt
|
|
SETD.1 Status
|
|
LDA.1
|
|
INIB 0d1
|
|
XOR
|
|
BRQ alignFromProgram
|
|
SETD.2 DataAt
|
|
alignFromProgram:
|
|
CALL numSet
|
|
|
|
alignTakeLoop:
|
|
SETD.0 AlignLeft
|
|
SETD.2 ClsWord
|
|
CALL numCompare
|
|
BRC alignRemainder ; What is left is smaller than the step, so that is the rest.
|
|
SETD.0 AlignLeft
|
|
SETD.2 ClsWord
|
|
CALL numTake
|
|
BRI alignTakeLoop
|
|
|
|
alignRemainder:
|
|
; Already on a boundary means no zeroes at all, not a whole step of them.
|
|
SETD.0 AlignLeft
|
|
LDA.0
|
|
INCD.0
|
|
LDB.0
|
|
OR
|
|
BRQ alignNone
|
|
SETD.0 RunLength
|
|
SETD.2 ClsWord
|
|
CALL numSet
|
|
SETD.0 RunLength
|
|
SETD.2 AlignLeft
|
|
CALL numTake
|
|
BRI alignDone
|
|
|
|
alignNone:
|
|
SETD.0 RunLength
|
|
CALL numZero
|
|
|
|
alignDone:
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
alignByZero:
|
|
SETD.0 AlignZeroText
|
|
CALL clsComplain
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; RunLength bytes of nothing: the cursor moves over them in either pass, and the second
|
|
; one writes them out as well.
|
|
layDownRun:
|
|
SETD.0 RunAt
|
|
CALL numZero
|
|
layDownLoop:
|
|
SETD.0 RunAt
|
|
SETD.2 RunLength
|
|
CALL numCompare
|
|
BNC layDownDone
|
|
SETD.0 Emitting
|
|
LDA.0
|
|
BRA layDownStep
|
|
RSTA
|
|
CALL emitByte
|
|
layDownStep:
|
|
SETD.2 OneWord
|
|
CALL moveCursor
|
|
SETD.0 RunAt
|
|
CALL numStep
|
|
BRI layDownLoop
|
|
layDownDone:
|
|
RET
|
|
|
|
; Writes a zero over the colon on the end of a label definition.
|
|
dropColon:
|
|
SETD.0 TokLength
|
|
LDA.0
|
|
BRA dropColonDone
|
|
DECA
|
|
SETD.0 TokLength
|
|
STA.0
|
|
SETD.0 TokText
|
|
SETD.1 DropWalk
|
|
STD.0.1
|
|
SETD.0 DropWalk
|
|
SETD.2 TokLength
|
|
LDA.2
|
|
CALL numAddByte
|
|
SETD.1 DropWalk
|
|
LDD.0.1
|
|
RSTA
|
|
STA.0
|
|
dropColonDone:
|
|
RET
|
|
|
|
; ---- The output image ----
|
|
|
|
; Where each segment's bytes will go, and the header in front of them. Both lengths are
|
|
; known now, which is the whole reason the first pass exists.
|
|
; Which of the two file formats this is, settled once and written down.
|
|
;
|
|
; A program that says where it goes is a loadable one and gets the SBEX header; one that
|
|
; says nothing is a boot image and gets SPBT. The difference is not a version but a
|
|
; question of what the file needs of whatever reads it.
|
|
;
|
|
; ONE FLAG RATHER THAN THE TEST REPEATED. It is asked in seven places - the header, the
|
|
; extension, where the vectors go, whether they carry a marker, how long the file is, and
|
|
; whether a Boot line is the entry or a handler - and written out each time it read as an
|
|
; OR of the two bases, whose sense is the opposite of what most of those places want. 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.
|
|
settleFormat:
|
|
RSTA
|
|
SETD.0 Loadable
|
|
STA.0
|
|
SETD.0 ProgBased
|
|
LDA.0
|
|
SETD.2 DataBased
|
|
LDB.2
|
|
OR
|
|
BRQ settleFormatDone
|
|
INIA 0d1
|
|
SETD.0 Loadable
|
|
STA.0
|
|
settleFormatDone:
|
|
RET
|
|
|
|
layOutImage:
|
|
; How long each segment came out, which is where its cursor ended less where it began.
|
|
SETD.0 ImgProgLen
|
|
SETD.2 ProgAt
|
|
CALL numSet
|
|
SETD.0 ImgProgLen
|
|
SETD.2 ProgBase
|
|
CALL numTake
|
|
SETD.0 ImgDataLen
|
|
SETD.2 DataAt
|
|
CALL numSet
|
|
SETD.0 ImgDataLen
|
|
SETD.2 DataBase
|
|
CALL numTake
|
|
|
|
SETD.0 Loadable
|
|
LDA.0
|
|
BNA layOutLoadable
|
|
|
|
; ---- A boot image ----
|
|
;
|
|
; Nineteen bytes of format: the magic, a version, four feature flags, and a marker and a
|
|
; length in front of each of the two segments.
|
|
SETD.0 ImgTotal
|
|
SETD.2 ImgProgLen
|
|
CALL numSet
|
|
SETD.0 ImgTotal
|
|
SETD.2 ImgDataLen
|
|
CALL numAdd
|
|
INIA 0d19
|
|
SETD.0 ImgTotal
|
|
CALL numAddByte
|
|
CALL checkImageRoom
|
|
BNQ layOutNo
|
|
|
|
SETD.0 ImgWalk
|
|
CALL numZero ; The front of the file, not the front of a buffer.
|
|
SETD.0 MagicSPBT
|
|
INIA 0d4
|
|
CALL putBytes
|
|
INIA 0d1
|
|
CALL putByte ; The format version.
|
|
RSTA
|
|
CALL putByte
|
|
CALL putByte
|
|
CALL putByte
|
|
CALL putByte ; Four bytes of feature flags, none of them asked for.
|
|
SETD.0 MagicPRG
|
|
INIA 0d3
|
|
CALL putBytes
|
|
SETD.0 ImgProgLen
|
|
CALL putWord
|
|
|
|
; The program bytes go where the walk has reached, and the marker between the segments
|
|
; sits after them.
|
|
SETD.0 ProgPut
|
|
SETD.2 ImgWalk
|
|
CALL numSet
|
|
SETD.0 ImgWalk
|
|
SETD.2 ImgProgLen
|
|
CALL numAdd
|
|
SETD.0 MagicDAT
|
|
INIA 0d3
|
|
CALL putBytes
|
|
SETD.0 ImgDataLen
|
|
CALL putWord
|
|
SETD.0 DataPut
|
|
SETD.2 ImgWalk
|
|
CALL numSet
|
|
BRI layOutYes
|
|
|
|
layOutLoadable:
|
|
; ---- A loadable program ----
|
|
;
|
|
; Sixteen bytes, so the code begins at a round offset and finding it is one step. Nothing
|
|
; here relocates anything: the addresses are where the program was built to live.
|
|
SETD.0 ImgTotal
|
|
SETD.2 ImgProgLen
|
|
CALL numSet
|
|
SETD.0 ImgTotal
|
|
SETD.2 ImgDataLen
|
|
CALL numAdd
|
|
INIA 0d16
|
|
SETD.0 ImgTotal
|
|
CALL numAddByte
|
|
CALL checkImageRoom
|
|
BNQ layOutNo
|
|
|
|
SETD.0 ImgWalk
|
|
CALL numZero ; The front of the file, not the front of a buffer.
|
|
SETD.0 MagicSBEX
|
|
INIA 0d4
|
|
CALL putBytes
|
|
INIA 0d1
|
|
CALL putByte ; Version one: it brings no vectors.
|
|
RSTA
|
|
CALL putByte ; And says so again, as a count of none.
|
|
SETD.0 ProgBase
|
|
CALL putWord
|
|
; Where to start. Without a Boot line that is the first byte of the code, which is where
|
|
; a program with nothing to say about it begins.
|
|
SETD.0 ProgBase
|
|
CALL putWord
|
|
SETD.0 ImgProgLen
|
|
CALL putWord
|
|
SETD.0 DataBase
|
|
CALL putWord
|
|
SETD.0 ImgDataLen
|
|
CALL putWord
|
|
|
|
SETD.0 ProgPut
|
|
SETD.2 ImgWalk
|
|
CALL numSet
|
|
SETD.0 DataPut
|
|
SETD.2 ImgWalk
|
|
CALL numSet
|
|
SETD.0 DataPut
|
|
SETD.2 ImgProgLen
|
|
CALL numAdd
|
|
|
|
layOutYes:
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
layOutNo:
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; ---- Room for the file ----
|
|
;
|
|
; Nothing is held in memory any more, so what is checked here is not a buffer. It is that
|
|
; the SIZE CAN STILL BE DESCRIBED: every number in this assembler is two bytes, so a file
|
|
; of more than 65,535 would have wrapped round while it was being added up and come out
|
|
; smaller than one of its own segments. A wrapped total would allocate a short file and
|
|
; write off the end of it.
|
|
;
|
|
; Then the file is started, which is where running out of DISK is found - and that happens
|
|
; before a single byte is written, so a refusal costs nothing that was already there.
|
|
checkImageRoom:
|
|
SETD.0 ImgTotal
|
|
SETD.2 ImgProgLen
|
|
CALL numCompare
|
|
BRC imageTooBig ; The total is smaller than a part of it: it went round.
|
|
SETD.0 ImgTotal
|
|
SETD.2 ImgDataLen
|
|
CALL numCompare
|
|
BRC imageTooBig
|
|
|
|
; ---- How much room to ask for ----
|
|
;
|
|
; MORE THAN THE FILE WILL COME TO, on purpose. How many vectors are actually installed is
|
|
; not known until the second pass has resolved every handler, and by then the file has to
|
|
; exist to be written into. What IS known now is how many vectors were DECLARED, and no
|
|
; more than that can be installed - so the room asked for is the whole file plus four
|
|
; bytes for each of them and five for a marker.
|
|
;
|
|
; Asking for too much costs nothing but a moment: osFileDone is told what it really came
|
|
; to and the difference goes back. Asking for too little would have meant writing off the
|
|
; end of the file, and the first sign of it was Keys coming out four bytes short.
|
|
SETD.0 ImgAsk
|
|
SETD.2 ImgTotal
|
|
CALL numSet
|
|
SETD.0 ImgVecMost
|
|
SETD.2 VecCount
|
|
CALL numSet
|
|
SETD.0 ImgVecMost
|
|
SETD.2 ImgVecMost
|
|
CALL numAdd
|
|
SETD.0 ImgVecMost
|
|
SETD.2 ImgVecMost
|
|
CALL numAdd ; Four bytes an entry.
|
|
SETD.0 ImgAsk
|
|
SETD.2 ImgVecMost
|
|
CALL numAdd
|
|
INIA 0d5
|
|
SETD.0 ImgAsk
|
|
CALL numAddByte
|
|
|
|
; Blocks are the high half of the size and the tail is the low half, which is how an
|
|
; entry holds one and why nothing here has to divide.
|
|
SETD.1 ImgAsk
|
|
LDA.1
|
|
SETD.0 ImgOutBlocks
|
|
STA.0 ; The high half is whole blocks.
|
|
SETD.1 ImgAsk
|
|
INCD.1
|
|
LDA.1
|
|
SETD.0 ImgOutTail
|
|
STA.0 ; And the low half is what is left over.
|
|
|
|
; DP3 wants the block count and A the tail. The Stack takes the high byte first, which is
|
|
; the way every reader in the system takes a count back out of DP3.
|
|
RSTA
|
|
PSHA
|
|
SETD.0 ImgOutBlocks
|
|
LDA.0
|
|
PSHA
|
|
POPD.3
|
|
SETD.0 ImgOutTail
|
|
LDA.0
|
|
SETD.0 OutName
|
|
SWI osFileStart
|
|
BNQ imageNoRoom
|
|
|
|
; Nothing in the window yet.
|
|
RSTA
|
|
SETD.0 OutHeld
|
|
STA.0
|
|
SETD.0 OutDirty
|
|
STA.0
|
|
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
imageNoRoom:
|
|
SETD.0 NoRoomText
|
|
SWI osPrintString
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
imageTooBig:
|
|
SETD.0 TooBigText
|
|
SWI osPrintString
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; Puts A down at ImgWalk and steps it.
|
|
; ---- Putting a byte into the file ----
|
|
;
|
|
; THE FILE IS WRITTEN AS IT IS MADE, through one block of window, rather than being built
|
|
; whole in memory and handed over at the end. Everything below works in FILE OFFSETS: a
|
|
; cursor is a two byte number counting from the front of the file, and because a block is
|
|
; two hundred and fifty six bytes, the block it lands in is the offset's HIGH byte and the
|
|
; place within that block is its LOW one. No division anywhere, which is just as well.
|
|
;
|
|
; There are two cursors and they move independently, because #Program and #Data alternate
|
|
; in source and the two segments are consecutive in the file. Whenever a byte lands in a
|
|
; block other than the one in hand, the one in hand goes back to the disk and the new one
|
|
; is fetched - so a run of bytes in one segment costs nothing extra, and a switch between
|
|
; segments costs two block operations. There are a few dozen switches in a source file and
|
|
; several thousand bytes.
|
|
;
|
|
; The block is FETCHED rather than assumed blank, and that is what makes the whole thing
|
|
; work with one window instead of three. The header is patched after every byte is out; the
|
|
; block where the program ends is the same block the data begins in; both are simply
|
|
; revisits, and a revisit is what fetching handles.
|
|
|
|
; A at the file offset in the two byte number DP2 names, which is then stepped on.
|
|
putAt:
|
|
SETD.0 ImgHold
|
|
STA.0
|
|
|
|
; WHICH CURSOR THIS IS, KEPT IN MEMORY. It arrives in DP2 and everything below wants DP2
|
|
; for something of its own - and a call puts DP2 back the way it was AT THE CALL, not the
|
|
; way it was on the way in. Leaving it there meant the step at the end moved whatever the
|
|
; last call had left in DP2, which was the address of the window, and the window walked
|
|
; off across memory while the cursor stood still.
|
|
SETD.0 ImgCursor
|
|
STD.2.0
|
|
|
|
; Which block, and where in it: a block is two hundred and fifty six bytes, so the two
|
|
; halves of the offset are exactly those two things.
|
|
PSHD.2
|
|
POPD.0
|
|
LDA.0
|
|
SETD.1 ImgBlockWant
|
|
STA.1
|
|
INCD.0
|
|
LDA.0
|
|
SETD.1 ImgSlot
|
|
STA.1
|
|
|
|
CALL outHold
|
|
BNQ putAtFailed
|
|
|
|
SETD.0 OutAt
|
|
SETD.2 ScratchWindow
|
|
CALL numSet
|
|
SETD.0 OutAt
|
|
SETD.1 ImgSlot
|
|
LDA.1
|
|
CALL numAddByte
|
|
SETD.1 OutAt
|
|
LDD.0.1
|
|
SETD.1 ImgHold
|
|
LDA.1
|
|
STA.0
|
|
|
|
INIA 0x01
|
|
SETD.0 OutDirty
|
|
STA.0
|
|
|
|
SETD.1 ImgCursor
|
|
LDD.0.1
|
|
CALL numStep
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
putAtFailed:
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; Makes sure the block ImgBlockWant names is the one in the window, writing back whatever
|
|
; was there and fetching the new one.
|
|
outHold:
|
|
SETD.0 OutHeld
|
|
LDA.0
|
|
BRA outFetch ; Nothing in hand at all.
|
|
SETD.0 OutBlock
|
|
LDA.0
|
|
SETD.2 ImgBlockWant
|
|
LDB.2
|
|
CCF
|
|
SUB
|
|
BRQ outHeldAlready
|
|
|
|
CALL outFlush
|
|
BNQ outHoldNo
|
|
|
|
outFetch:
|
|
SETD.0 ImgBlockWant
|
|
LDA.0
|
|
SETD.1 OutBlock
|
|
STA.1
|
|
|
|
SETD.1 ScratchWindow
|
|
LDD.1.1
|
|
SETD.0 OutBlock
|
|
LDB.0
|
|
RSTA
|
|
SWI osFileFetch
|
|
BNQ outHoldNo
|
|
|
|
INIA 0x01
|
|
SETD.0 OutHeld
|
|
STA.0
|
|
RSTA
|
|
SETD.0 OutDirty
|
|
STA.0
|
|
|
|
outHeldAlready:
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
outHoldNo:
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; Whatever is in the window goes back to the file, if anything has been put in it.
|
|
outFlush:
|
|
SETD.0 OutHeld
|
|
LDA.0
|
|
BRA outFlushNothing
|
|
SETD.0 OutDirty
|
|
LDA.0
|
|
BRA outFlushNothing
|
|
|
|
SETD.1 ScratchWindow
|
|
LDD.1.1
|
|
SETD.0 OutBlock
|
|
LDB.0
|
|
RSTA
|
|
SWI osFileWrite
|
|
BNQ outHoldNo
|
|
|
|
RSTA
|
|
SETD.0 OutDirty
|
|
STA.0
|
|
|
|
outFlushNothing:
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; The header and everything else that is written in order goes through ImgWalk.
|
|
putByte:
|
|
SETD.2 ImgWalk
|
|
CALL putAt
|
|
RET
|
|
|
|
; Puts A bytes from DP0 down at ImgWalk.
|
|
putBytes:
|
|
SETD.1 ImgCount
|
|
STA.1
|
|
SETD.1 ImgFrom
|
|
STD.0.1
|
|
putBytesLoop:
|
|
SETD.0 ImgCount
|
|
LDA.0
|
|
BRA putBytesDone
|
|
DECA
|
|
STA.0
|
|
SETD.1 ImgFrom
|
|
LDD.0.1
|
|
LDA.0
|
|
CALL putByte
|
|
SETD.0 ImgFrom
|
|
CALL numStep
|
|
BRI putBytesLoop
|
|
putBytesDone:
|
|
RET
|
|
|
|
; Puts the two byte number at DP0 down at ImgWalk, most significant first, the way every
|
|
; number in this format is stored.
|
|
putWord:
|
|
SETD.1 ImgFrom
|
|
STD.0.1
|
|
LDA.0
|
|
CALL putByte
|
|
SETD.1 ImgFrom
|
|
LDD.0.1
|
|
INCD.0
|
|
LDA.0
|
|
CALL putByte
|
|
RET
|
|
|
|
; Puts A into whichever segment is open, and steps that segment's pointer.
|
|
emitByte:
|
|
SETD.0 EmitHold
|
|
STA.0
|
|
SETD.0 Status
|
|
LDA.0
|
|
INIB 0d1
|
|
XOR
|
|
BRQ emitToProgram
|
|
SETD.1 DataPut
|
|
BRI emitPut
|
|
emitToProgram:
|
|
SETD.1 ProgPut
|
|
emitPut:
|
|
PSHD.1
|
|
POPD.2 ; ProgPut or DataPut, whichever this byte belongs to.
|
|
SETD.0 EmitHold
|
|
LDA.0
|
|
CALL putAt
|
|
RET
|
|
|
|
; The byte at DP0 offset by A, into ClsByte. The classifier has one of these; this is the
|
|
; assembler's, because a routine over there answers into a variable over there.
|
|
byteAt:
|
|
PSHA
|
|
PSHD.0
|
|
POPB
|
|
POPA
|
|
SETD.0 EmitWalk
|
|
STA.0
|
|
INCD.0
|
|
STB.0
|
|
POPA
|
|
SETD.0 EmitWalk
|
|
CALL numAddByte
|
|
SETD.1 EmitWalk
|
|
LDD.0.1
|
|
LDA.0
|
|
SETD.0 ClsByte
|
|
STA.0
|
|
RET
|
|
|
|
; ---- The vectors the file carries ----
|
|
;
|
|
; Last in the file, after the code and the data, so that everything before them sits where
|
|
; a loader that knows nothing about vectors already expects to find it.
|
|
;
|
|
; Four bytes each: WHERE THE SLOT IS, then what goes in it. Saying the slot outright rather
|
|
; than the vector number means the loader does no arithmetic and does not have to know
|
|
; where either vector table begins, and one entry can name a software or a hardware vector
|
|
; without saying which it is.
|
|
writeVectors:
|
|
SETD.0 VecInstalled
|
|
CALL numZero
|
|
SETD.0 EntryAddress
|
|
SETD.2 ProgBase
|
|
CALL numSet
|
|
|
|
; Counted first, because a boot image writes the length of the run before the run.
|
|
CALL countVectors
|
|
SETD.0 ImgWalk
|
|
SETD.2 DataPut
|
|
CALL numSet
|
|
|
|
SETD.0 VecInstalled
|
|
LDA.0
|
|
INCD.0
|
|
LDB.0
|
|
OR
|
|
BRQ writeVectorsNone
|
|
|
|
SETD.0 Loadable
|
|
LDA.0
|
|
BNA writeVectorsRoom
|
|
|
|
; A boot image marks the run and says how long it is, the way it does for its segments.
|
|
SETD.0 MagicVEC
|
|
INIA 0d3
|
|
CALL putBytes
|
|
SETD.0 VecBytes
|
|
CALL putWord
|
|
|
|
writeVectorsRoom:
|
|
SETD.0 VecWhich
|
|
CALL numZero
|
|
writeVectorLoop:
|
|
SETD.0 VecWhich
|
|
SETD.2 VecCount
|
|
CALL numCompare
|
|
BNC writeVectorsDone
|
|
CALL vecSlotAt
|
|
CALL vecReadFields
|
|
SETD.0 VecHasHandler
|
|
LDA.0
|
|
BRA writeVectorNext
|
|
CALL vectorIsEntry
|
|
BRQ writeVectorNext ; Boot in a loadable program is the entry, not a handler.
|
|
|
|
; Where the slot is: the table it belongs to, plus two bytes for every vector before it.
|
|
SETD.0 SlotAt
|
|
SETD.2 SoftwareBase
|
|
CALL numSet
|
|
SETD.0 VecBase
|
|
LDA.0
|
|
BRA writeVectorSlot
|
|
SETD.0 SlotAt
|
|
SETD.2 HardwareBase
|
|
CALL numSet
|
|
writeVectorSlot:
|
|
SETD.0 VecNumber
|
|
LDA.0
|
|
SETD.0 SlotAt
|
|
CALL numAddByte
|
|
SETD.0 VecNumber
|
|
LDA.0
|
|
SETD.0 SlotAt
|
|
CALL numAddByte
|
|
SETD.0 SlotAt
|
|
CALL putWord
|
|
SETD.0 VecHandler
|
|
CALL putWord
|
|
|
|
writeVectorNext:
|
|
SETD.0 VecWhich
|
|
CALL numStep
|
|
BRI writeVectorLoop
|
|
|
|
writeVectorsDone:
|
|
SETD.0 ImgTotal
|
|
SETD.2 VecBytes
|
|
CALL numAdd
|
|
SETD.0 Loadable
|
|
LDA.0
|
|
BNA writeVectorsHeader
|
|
INIA 0d5
|
|
SETD.0 ImgTotal
|
|
CALL numAddByte ; The marker and the length in front of them.
|
|
|
|
writeVectorsHeader:
|
|
; A file that brings vectors needs something of its loader a version one loader does not
|
|
; know how to give, so it says version two and an older one refuses it rather than
|
|
; running the program without its handlers.
|
|
SETD.0 Loadable
|
|
LDA.0
|
|
BRA writeVectorsNone
|
|
SETD.0 ImgWalk
|
|
CALL numZero ; Back to the front of the file, which is a revisit like
|
|
; any other: the window fetches block nought again.
|
|
SETD.0 ImgWalk
|
|
INIA 0d4
|
|
CALL numAddByte
|
|
INIA 0d2
|
|
CALL putByte
|
|
SETD.0 VecInstalled
|
|
INCD.0
|
|
LDA.0
|
|
CALL putByte
|
|
|
|
writeVectorsNone:
|
|
; And where to start, which a Boot line fills in and everything else leaves as the first
|
|
; byte of the code.
|
|
SETD.0 Loadable
|
|
LDA.0
|
|
BRA writeVectorsOut
|
|
SETD.0 ImgWalk
|
|
CALL numZero ; Back to the front of the file, which is a revisit like
|
|
; any other: the window fetches block nought again.
|
|
SETD.0 ImgWalk
|
|
INIA 0d8
|
|
CALL numAddByte
|
|
SETD.0 EntryAddress
|
|
CALL putWord
|
|
|
|
writeVectorsOut:
|
|
RET
|
|
|
|
; How many vectors will be written, into VecInstalled, and how many bytes that is.
|
|
countVectors:
|
|
SETD.0 VecWhich
|
|
CALL numZero
|
|
countVectorLoop:
|
|
SETD.0 VecWhich
|
|
SETD.2 VecCount
|
|
CALL numCompare
|
|
BNC countVectorsDone
|
|
CALL vecSlotAt
|
|
CALL vecReadFields
|
|
SETD.0 VecHasHandler
|
|
LDA.0
|
|
BRA countVectorNext
|
|
CALL vectorIsEntry
|
|
BRQ countVectorTakeEntry
|
|
SETD.0 VecInstalled
|
|
CALL numStep
|
|
BRI countVectorNext
|
|
countVectorTakeEntry:
|
|
SETD.0 EntryAddress
|
|
SETD.2 VecHandler
|
|
CALL numSet
|
|
countVectorNext:
|
|
SETD.0 VecWhich
|
|
CALL numStep
|
|
BRI countVectorLoop
|
|
|
|
countVectorsDone:
|
|
SETD.0 VecBytes
|
|
SETD.2 VecInstalled
|
|
CALL numSet
|
|
SETD.0 VecBytes
|
|
SETD.2 VecBytes
|
|
CALL numAdd
|
|
SETD.0 VecBytes
|
|
SETD.2 VecBytes
|
|
CALL numAdd ; Four bytes an entry.
|
|
RET
|
|
|
|
; Q is zero if this entry is the Boot line of a LOADABLE program, which fills the entry
|
|
; field instead of being installed. Vector zero is where the whole machine starts, and a
|
|
; program being 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.
|
|
vectorIsEntry:
|
|
SETD.0 Loadable
|
|
LDA.0
|
|
BRA vectorNotEntry
|
|
SETD.0 VecBase
|
|
LDA.0
|
|
BNA vectorNotEntry
|
|
SETD.0 VecNumber
|
|
LDA.0
|
|
BNA vectorNotEntry
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
vectorNotEntry:
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; Whatever is still in the window, and then the file takes its name. Nothing that was
|
|
; already on the disk has been touched until this last step.
|
|
writeImage:
|
|
CALL outFlush
|
|
BNQ writeFailed
|
|
|
|
; What it really came to, which is what the room was asked for less whatever the vectors
|
|
; did not need.
|
|
SETD.1 ImgTotal
|
|
LDA.1
|
|
SETD.0 ImgOutBlocks
|
|
STA.0
|
|
SETD.1 ImgTotal
|
|
INCD.1
|
|
LDA.1
|
|
SETD.0 ImgOutTail
|
|
STA.0
|
|
|
|
RSTA
|
|
PSHA
|
|
SETD.0 ImgOutBlocks
|
|
LDA.0
|
|
PSHA
|
|
POPD.3
|
|
SETD.0 ImgOutTail
|
|
LDA.0
|
|
SWI osFileDone
|
|
BNQ writeFailed
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
writeFailed:
|
|
SETD.0 NoWriteText
|
|
SWI osPrintString
|
|
SETD.0 OutName
|
|
SWI osPrintString
|
|
SETD.0 NewLine
|
|
SWI osPrintString
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; What the source file is called with its extension replaced, so that hello.asm becomes
|
|
; hello.bin without anybody having to say so twice.
|
|
deriveName:
|
|
SETD.0 Argument
|
|
SETD.1 OutName
|
|
CALL copyName
|
|
SETD.0 OutName
|
|
SETD.1 DotAt
|
|
STD.0.1
|
|
SETD.0 DotFound
|
|
CALL numZero
|
|
|
|
SETD.0 OutName
|
|
SETD.1 NameWalk
|
|
STD.0.1
|
|
deriveLoop:
|
|
SETD.1 NameWalk
|
|
LDD.0.1
|
|
LDA.0
|
|
BRA deriveEnd
|
|
INIB 0x2E ; '.'
|
|
XOR
|
|
BNQ deriveStep
|
|
SETD.0 DotAt
|
|
SETD.2 NameWalk
|
|
CALL numSet
|
|
INIA 0d1
|
|
SETD.0 DotFound
|
|
STA.0
|
|
deriveStep:
|
|
SETD.0 NameWalk
|
|
CALL numStep
|
|
BRI deriveLoop
|
|
|
|
deriveEnd:
|
|
SETD.0 DotFound
|
|
LDA.0
|
|
BNA deriveAtDot
|
|
SETD.0 DotAt
|
|
SETD.2 NameWalk
|
|
CALL numSet ; No extension at all, so the new one goes on the end.
|
|
deriveAtDot:
|
|
SETD.1 DotAt
|
|
LDD.1.1
|
|
SETD.0 Loadable
|
|
LDA.0
|
|
BNA deriveLoadable
|
|
SETD.0 ExtensionBin
|
|
BRI deriveCopy
|
|
deriveLoadable:
|
|
SETD.0 ExtensionSbx
|
|
deriveCopy:
|
|
LDA.0
|
|
STA.1
|
|
BRA deriveDone
|
|
INCD.0
|
|
INCD.1
|
|
BRI deriveCopy
|
|
deriveDone:
|
|
RET
|
|
|
|
; Copies the string at DP0 to DP1, up to 22 characters and the zero after them.
|
|
copyName:
|
|
INIA 0d22
|
|
SETD.2 NameLeft
|
|
STA.2
|
|
copyNameLoop:
|
|
LDA.0
|
|
BRA copyNameEnd
|
|
STA.1
|
|
INCD.0
|
|
INCD.1
|
|
SETD.2 NameLeft
|
|
LDA.2
|
|
DECA
|
|
STA.2
|
|
BNA copyNameLoop
|
|
copyNameEnd:
|
|
RSTA
|
|
STA.1
|
|
RET
|
|
|
|
report:
|
|
SETD.0 WroteText
|
|
SWI osPrintString
|
|
SETD.0 OutName
|
|
SWI osPrintString
|
|
SETD.0 ProgramText
|
|
SWI osPrintString
|
|
SETD.0 ImgProgLen
|
|
LDA.0
|
|
INCD.0
|
|
LDB.0
|
|
SWI osPrintNumber
|
|
SETD.0 DataText
|
|
SWI osPrintString
|
|
SETD.0 ImgDataLen
|
|
LDA.0
|
|
INCD.0
|
|
LDB.0
|
|
SWI osPrintNumber
|
|
SETD.0 LabelsText
|
|
SWI osPrintString
|
|
SETD.0 LabCount
|
|
LDA.0
|
|
INCD.0
|
|
LDB.0
|
|
SWI osPrintNumber
|
|
SETD.0 LabelsEnd
|
|
SWI osPrintString
|
|
RET
|
|
|
|
#Data
|
|
|
|
#Base 0x2000
|
|
|
|
Argument:
|
|
#Reserve 0d23
|
|
OutName:
|
|
#Reserve 0d27
|
|
NameWalk:
|
|
0x00 0x00
|
|
NameLeft:
|
|
0x00
|
|
DotAt:
|
|
0x00 0x00
|
|
DotFound:
|
|
0x00 0x00
|
|
|
|
Status:
|
|
0x00
|
|
AfterSwi:
|
|
0x00
|
|
Emitting:
|
|
0x00
|
|
ProgAt:
|
|
0x00 0x00
|
|
DataAt:
|
|
0x00 0x00
|
|
ProgBase:
|
|
0x00 0x00
|
|
DataBase:
|
|
0x00 0x00
|
|
ProgBased:
|
|
0x00
|
|
Loadable:
|
|
0x00
|
|
DataBased:
|
|
0x00
|
|
ProgUsed:
|
|
0x00
|
|
DataUsed:
|
|
0x00
|
|
ImgProgLen:
|
|
0x00 0x00
|
|
ImgDataLen:
|
|
0x00 0x00
|
|
RunLength:
|
|
0x00 0x00
|
|
RunAt:
|
|
0x00 0x00
|
|
AlignLeft:
|
|
0x00 0x00
|
|
OneWord:
|
|
0x00 0x01
|
|
VecLineWas:
|
|
0x00 0x00
|
|
VecPinned:
|
|
0x00
|
|
VecPinnedGiven:
|
|
0x00
|
|
VecHandlerHere:
|
|
0x00
|
|
VecIsDevice:
|
|
0x00
|
|
VecInstalled:
|
|
0x00 0x00
|
|
VecBytes:
|
|
0x00 0x00
|
|
EntryAddress:
|
|
0x00 0x00
|
|
SlotAt:
|
|
0x00 0x00
|
|
SoftwareBase:
|
|
0xFC 0x00
|
|
HardwareBase:
|
|
0xFE 0x00
|
|
ReservedLeft:
|
|
0x00
|
|
ReservedWalk:
|
|
0x00 0x00
|
|
ReservedCount:
|
|
0d5
|
|
VecHandlerName:
|
|
#Reserve 0d23
|
|
|
|
; A name nothing can type, so that devices - which have no names of their own - can live in
|
|
; a table that refuses a repeated one. A space, then a counter.
|
|
DeviceName:
|
|
0x20 0x01 0x00
|
|
#Reserve 0d20
|
|
|
|
; The vectors that already mean something. Sixteen bytes each: fifteen of name, then the
|
|
; number the machine fixed for it.
|
|
ReservedNames:
|
|
"Boot"
|
|
#Reserve 0d10
|
|
0d0
|
|
"SoftReset"
|
|
#Reserve 0d5
|
|
0d1
|
|
"BadOpcode"
|
|
#Reserve 0d5
|
|
0d2
|
|
"GuardViolation"
|
|
0d3
|
|
"BankFault"
|
|
#Reserve 0d5
|
|
0d4
|
|
|
|
ProgPut:
|
|
0x00 0x00
|
|
DataPut:
|
|
0x00 0x00
|
|
EmitHold:
|
|
0x00
|
|
EmitKind:
|
|
0x00
|
|
EmitLeft:
|
|
0x00
|
|
EmitWalk:
|
|
0x00 0x00
|
|
|
|
ImgTotal:
|
|
0x00 0x00
|
|
ImgWalk:
|
|
0x00 0x00
|
|
ImgFrom:
|
|
0x00 0x00
|
|
ImgCount:
|
|
0x00
|
|
ImgHold:
|
|
0x00
|
|
DropWalk:
|
|
0x00 0x00
|
|
|
|
; How big an output file this can build. Everything the assembler makes has to fit here at once,
|
|
; because a file is written in one call and there is nowhere to put half of one. CosmOS
|
|
; itself comes to 9,564 bytes.
|
|
; Eighteen kilobytes, and IT HAS TO AGREE WITH THE SCRATCH MAP, which says where that room
|
|
; actually is. Three numbers in three files describe these buffers - this one, LabLimit and
|
|
; LabRoom in labels.asm, and the map itself - and each of the three has now been the one
|
|
; that was left behind while the other two moved.
|
|
; Which block of the file is in the window, and whether anything has been put in it. The
|
|
; window itself is in scratch, like every other buffer here: a #Reserve is written into the
|
|
; file as zeroes and copied at load, and a program that carries its own scratch pays for it
|
|
; twice.
|
|
OutAt:
|
|
0x00 0x00
|
|
OutBlock:
|
|
0x00
|
|
OutHeld:
|
|
0x00
|
|
OutDirty:
|
|
0x00
|
|
ImgBlockWant:
|
|
0x00
|
|
ImgSlot:
|
|
0x00
|
|
ImgOutBlocks:
|
|
0x00
|
|
ImgOutTail:
|
|
0x00
|
|
ImgCursor:
|
|
0x00 0x00
|
|
ImgAsk:
|
|
0x00 0x00
|
|
ImgVecMost:
|
|
0x00 0x00
|
|
NoRoomText:
|
|
"no room on the disk for the output
|
|
"
|
|
|
|
MagicSPBT:
|
|
"SPBT"
|
|
MagicSBEX:
|
|
"SBEX"
|
|
MagicPRG:
|
|
"PRG"
|
|
MagicDAT:
|
|
"DAT"
|
|
MagicVEC:
|
|
"VEC"
|
|
ExtensionBin:
|
|
".bin"
|
|
ExtensionSbx:
|
|
".sbx"
|
|
|
|
WordProgram:
|
|
"#Program"
|
|
WordData:
|
|
"#Data"
|
|
WordVectors:
|
|
"#Vectors"
|
|
WordBase:
|
|
"#Base"
|
|
WordInclude:
|
|
"#Include"
|
|
WordReserve:
|
|
"#Reserve"
|
|
WordAlign:
|
|
"#Align"
|
|
WordDevice:
|
|
"Device"
|
|
|
|
UsageText:
|
|
"say which file to assemble, as in: run hello.asm
|
|
"
|
|
NoSourceText:
|
|
"no such file: "
|
|
NewLine:
|
|
"
|
|
"
|
|
NowhereText:
|
|
"that has to be inside a segment, and no #Program or #Data has opened one"
|
|
NotProgramText:
|
|
"an instruction outside the Program Segment"
|
|
StringInProgramText:
|
|
"a string cannot go in the Program Segment, because an instruction cannot read it there"
|
|
LabelNowhereText:
|
|
"a label defined outside a segment, so there is nowhere for it to point"
|
|
UnknownText:
|
|
"no label of that name is defined anywhere in this program"
|
|
NotYetText:
|
|
"this assembler does not understand that directive yet"
|
|
BaseNowhereText:
|
|
"#Base outside a segment, so there is nothing for it to be the base of"
|
|
BaseLateText:
|
|
"#Base after something is already in the segment, and a base has to come first"
|
|
IncludeBareText:
|
|
"#Include with no file name after it
|
|
"
|
|
ReserveNowhereText:
|
|
"#Reserve outside a segment, so there is nothing there for it to move along"
|
|
AlignNowhereText:
|
|
"#Align outside a segment, so there is nothing there for it to move along"
|
|
AlignZeroText:
|
|
"#Align to a multiple of nothing"
|
|
NumberBareText:
|
|
"a directive with no number after it
|
|
"
|
|
NumberBadText:
|
|
"a directive wants a number here, written 0x.. or 0d.."
|
|
UnknownVectorText:
|
|
"no vector of that name is declared anywhere in this program"
|
|
FixedNumberText:
|
|
"that vector's number is fixed by the machine and is not anybody's to give"
|
|
BadNumberText:
|
|
"that is not a vector number that can be given: below 16 belongs to the machine, and 64
|
|
and up is handed out by the assembler"
|
|
VecTwiceText:
|
|
"that vector is declared more than once"
|
|
DisagreeNumberText:
|
|
"that vector was already given a different number, so the two sides disagree about which
|
|
vector this name means"
|
|
DeviceBareText:
|
|
"Device has to say which port, as a number, and then where to go"
|
|
NoHandlerText:
|
|
"there is no label of that name for the handler to be"
|
|
LostVectorText:
|
|
"a vector went missing between the two passes"
|
|
TooMuchText:
|
|
"there is more on that line than a vector entry can be"
|
|
VectorOddText:
|
|
"only names belong in the Vector Segment"
|
|
DisagreeText:
|
|
"the two passes disagree about how long this program is
|
|
"
|
|
BasesText:
|
|
"one segment says where it goes and the other does not. The one that says nothing lands
|
|
at zero, on top of whatever is there. Give both a #Base, or neither.
|
|
"
|
|
TooBigText:
|
|
"the output would be bigger than this assembler has room to build
|
|
"
|
|
NoWriteText:
|
|
"it would not write "
|
|
StoppedText:
|
|
"nothing was written
|
|
"
|
|
WroteText:
|
|
"wrote "
|
|
ProgramText:
|
|
": program "
|
|
DataText:
|
|
", data "
|
|
LabelsText:
|
|
", labels "
|
|
LabelsEnd:
|
|
"
|
|
"
|
|
|
|
|
|
#Include scratch.asm
|
|
#Include numbers.asm
|
|
#Include source.asm
|
|
#Include token.asm
|
|
#Include classify.asm
|
|
#Include labels.asm
|
|
#Include vectors.asm
|
|
#Include table.asm
|