> 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
1641 lines
29 KiB
NASM
1641 lines
29 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 binary 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 0x2000
|
|
|
|
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 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 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 ----
|
|
;
|
|
; A name on its own is a declaration and the assembler numbers it. A name with a number
|
|
; after it is pinned, which is how anything two separately assembled programs must agree
|
|
; about is fixed. A name with a HANDLER after it says this program implements the vector,
|
|
; and that needs a Vector Segment in the output file, which is not built yet.
|
|
;
|
|
; Which of the three it is cannot be known without looking at the next token, so the next
|
|
; token is looked at and handed back if it turns out to belong to the following line. A
|
|
; LINE is what tells them apart: two names on one line are a name and its handler, and two
|
|
; names on two lines are two declarations.
|
|
doVectorLine:
|
|
SETD.0 ClsType
|
|
LDA.0
|
|
INIB 0d5
|
|
XOR
|
|
BNQ vectorNotAName
|
|
|
|
SETD.0 TokText
|
|
SETD.1 VecName
|
|
CALL srcKeepName
|
|
SETD.0 VecLineWas
|
|
SETD.2 TokLine
|
|
CALL numSet
|
|
|
|
CALL tokNext
|
|
BNQ vectorAutomatic ; The file ended, so that was a declaration on its own.
|
|
|
|
SETD.0 TokLine
|
|
SETD.2 VecLineWas
|
|
CALL numCompare
|
|
BNQ vectorHandBack ; A different line, so it belongs to the next entry.
|
|
|
|
CALL clsToken
|
|
BNQ vectorFailed
|
|
SETD.0 ClsType
|
|
LDA.0
|
|
INIB 0d2
|
|
XOR
|
|
BNQ vectorHasHandler
|
|
|
|
; A pinned number. Anything after it on the same line would be a handler.
|
|
SETD.0 VecPinned
|
|
SETD.2 ClsValue
|
|
LDA.2
|
|
STA.0
|
|
CALL tokNext
|
|
BNQ vectorPinnedDone
|
|
SETD.0 TokLine
|
|
SETD.2 VecLineWas
|
|
CALL numCompare
|
|
BNQ vectorHandBackPinned
|
|
BRI vectorHasHandler
|
|
|
|
vectorPinnedDone:
|
|
SETD.0 VecPinned
|
|
LDA.0
|
|
CALL declareVector
|
|
RET
|
|
|
|
vectorHandBackPinned:
|
|
CALL tokBack
|
|
BRI vectorPinnedDone
|
|
|
|
vectorHandBack:
|
|
CALL tokBack
|
|
vectorAutomatic:
|
|
CALL vecTakeAuto
|
|
SETD.0 VecPutNumber
|
|
LDA.0
|
|
CALL declareVector
|
|
RET
|
|
|
|
; Writes VecName down with the number in A - but only in the first pass. The second one
|
|
; walks the same lines and must not declare anything again, the same way it does not add a
|
|
; label again: the table is the first pass's answer and the second pass only reads it.
|
|
declareVector:
|
|
SETD.0 VecTaking
|
|
STA.0
|
|
SETD.0 Emitting
|
|
LDA.0
|
|
BNA declareVectorSkip
|
|
SETD.0 VecName
|
|
SETD.2 VecTaking
|
|
LDA.2
|
|
CALL vecDeclare
|
|
RET
|
|
|
|
declareVectorSkip:
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
vectorHasHandler:
|
|
SETD.0 HandlerText
|
|
CALL clsComplain
|
|
BRI vectorFailed
|
|
|
|
vectorNotAName:
|
|
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
|
|
DPDN.0 0d01
|
|
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.
|
|
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
|
|
|
|
; 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.
|
|
SETD.0 ProgBased
|
|
LDA.0
|
|
SETD.2 DataBased
|
|
LDB.2
|
|
OR
|
|
BNQ 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 Image
|
|
SETD.1 ImgWalk
|
|
STD.0.1
|
|
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 Image
|
|
SETD.1 ImgWalk
|
|
STD.0.1
|
|
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
|
|
|
|
checkImageRoom:
|
|
SETD.0 ImgRoom
|
|
SETD.2 ImgTotal
|
|
CALL numCompare
|
|
BRC imageTooBig
|
|
RSTA
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
imageTooBig:
|
|
SETD.0 TooBigText
|
|
SWI osPrintString
|
|
RSTA
|
|
INIB 0d1
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; Puts A down at ImgWalk and steps it.
|
|
putByte:
|
|
SETD.0 ImgHold
|
|
STA.0
|
|
SETD.1 ImgWalk
|
|
LDD.0.1
|
|
SETD.2 ImgHold
|
|
LDA.2
|
|
STA.0
|
|
INCD.0
|
|
STD.0.1
|
|
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:
|
|
LDD.0.1
|
|
SETD.2 EmitHold
|
|
LDA.2
|
|
STA.0
|
|
INCD.0
|
|
STD.0.1
|
|
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
|
|
|
|
writeImage:
|
|
SETD.0 OutName
|
|
SETD.1 Image
|
|
SETD.2 ImgTotal
|
|
LDA.2
|
|
INCD.2
|
|
LDB.2
|
|
SWI osFileSave
|
|
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 ProgBased
|
|
LDA.0
|
|
SETD.2 DataBased
|
|
LDB.2
|
|
OR
|
|
BNQ 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 0x1000
|
|
|
|
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
|
|
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
|
|
VecTaking:
|
|
0x00
|
|
|
|
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 a binary 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.
|
|
ImgRoom:
|
|
0x10 0x00
|
|
|
|
MagicSPBT:
|
|
"SPBT"
|
|
MagicSBEX:
|
|
"SBEX"
|
|
MagicPRG:
|
|
"PRG"
|
|
MagicDAT:
|
|
"DAT"
|
|
ExtensionBin:
|
|
".bin"
|
|
ExtensionSbx:
|
|
".sbx"
|
|
|
|
WordProgram:
|
|
"#Program"
|
|
WordData:
|
|
"#Data"
|
|
WordVectors:
|
|
"#Vectors"
|
|
WordBase:
|
|
"#Base"
|
|
WordInclude:
|
|
"#Include"
|
|
WordReserve:
|
|
"#Reserve"
|
|
WordAlign:
|
|
"#Align"
|
|
|
|
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"
|
|
HandlerText:
|
|
"this assembler cannot build a Vector Segment yet, so it cannot install a handler"
|
|
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 binary 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:
|
|
"
|
|
"
|
|
|
|
Image:
|
|
#Reserve 0d4096
|
|
|
|
#Include numbers.asm
|
|
#Include source.asm
|
|
#Include token.asm
|
|
#Include classify.asm
|
|
#Include labels.asm
|
|
#Include vectors.asm
|
|
#Include table.asm
|