I'm not sure if it is of interest, but here what my assembler looks like:
[I follow the usual style: [...] are optionals etc.]
For now I only have implemented the original NMOS 6502 and 8085 as I was not working with other CPU types, but extension to CMOS or other architectures should be rather easy, if I ever need it.
Invocation:
Code:
rasm infile[.end][;/:] [output[.end]] [listfile]
With ';' the endings of the filenames are added if not given by the user, ':' the same but skips the listfile.
If the output has '.hex' the asm will produce no binary but an ASCII hex-file (with checksum, intended for serial transmission).
Operators:
Code:
+ - * / ;basic arithmetic
> ;Hi-Byte of value
< ;Lo-Byte of value
~ ;NOT
! ;OR
& ;AND
^;XOR
>> ;SHR
<< ;SHL
(..) ;parentheses for more complicated math - they can't be first place in a statement LDA (xx),y => you have to write LDA +(1+3)*5,y to be not confused w/ addressing mode
?[i]<label>[/i] ;returns 1 if the label is undefined or if a library label is unused
! ;returns 0 if the label is undefned or a library is unused
++ -- ;post- or pre-inc-/decrement, like in c
* ;also is the program counter
=,<>,>=,<= ;comparison, returns 1 or 0 if true or false
Those are my pseudops:
Code:
;this is a comment
/// and this
is a block of
/// coment -- for commenting out sections of code in a fast way
.CODE +|- ;turns code generation on or off, used for generating virutal tables:
*=0 ;zp space
.cod -
zpptr1 .wor 0
zpptr2 .wor 0
tmpbyte .byt0
.cod +
*=$c000 ;start of actual program
.ILL +|- ;allows or forbids NMOS undocumented opcodes
.CPU cpu ;defines target CPU: 6502,65N02,65N02_ILL,65C02,Rockwell65C02,WDC65C02,65C02S,WDC65C816 - but olny NMOS is fully implemented yet
The asm uses an enviroment variable, if none given it looks for .INC/.BIN/... in the directory 'include' inside program file's directory, recursing through all subdirectories:
Code:
.INC name ;includes the file "name" and processes it as asm-code
.BIN name|"name" [,+<skip>][,<len>] ;file is incorporated as binary, if +skip is given, <skip> bytes are skipped, if <len> is given this is the number of bytes to be incorporated.
.PATH "list" ;adds directories where to look for .inc - just like DOS PATH
.EXPORT "filename" ;defines the name of a file where to export label values - for generating ROM-maps
.EXPORT <libraryname>|<makname> ;macros or libraries are exported
The basic pseudops for data:
Code:
.BYTE <val>[,val...] ;list of byte values
.WORD <val>[,val...] ;liste of word values
.FILL <address>[,byteval=$FF] ;fill from * to address-1 with byte-val ($ff is default) - .e.g. for filling space till Vectors: .fill $fffa,$ea
.FLOAT <float>[,float] ;produces floating point data in 5--byte-CBM format
.GRAPH ;producing data for character maps or C64-Sprites
.GRAPH 10100011 ;single-color
.GRAPH ..--||## ;multi-color (..=00,--=01,||=10,##=11)
The pseudops for strings are more complicated. Strings can be translated (the CBM PETSCII is a little different from ASCII, so versatile translation is built in:
Code:
.ASC "string"|byteval[,...] ;liste of strings and/or byte values
.ASC "Hallo\$12\x34\255 ;= .asc "Hallo",$12,$34,$ff
.ASC00 ;like .asc but adds a 0 at the end of string
.ASC80 ;like .asc but the last character gets OR$80 (=for printstr routines or like the CBM BASIC token tables are done)
.ASCLEN ;like asc, but the length of the string is added [i]in front of[/i] the string, e.g. .asclen "Hallo" = .asc 5,"Hallo" - for fast pascal-style strings
.ASCREV ;like .asclen but the string is in reversed order .ascrev "Hallo" = .asc 5,"ollaH"
.TRANS byte[-byte]|"cc"=byte[-byte]|"cc"[,...] ;controls string translation:
.trans 1=2 ;in .asc-statemtents values of 1 would be replaced with 2
.trans "A-Z"="a-z" ;all capital letters would be replaced with their small counterpart
.trans "a"="^a" ;"a" would be replaced with control-a =$01, i.e. value and $3f
;| is like ^but value or $80; ~ is value and $3f or $80
.trans \<text>=<byte> ; defines an escape-sequence:
.trans \white=5 ;in .asc you could write "Hal\whitelo" => .asc "Hal",5,"lo"
.trans without parameters clears the translation table, i.e. every character is put to object file as is
.TRANS_PET graph|text ;.trans with values and escape-sequences for CBM PETSCII
.TRANS_POKE graph|text ;.trans with values fo CBM screen codes
Some pseudos for creating error messages or on-screen output:
Code:
.ERR message ;produce error message, e.g. a library needs some function defined which is forgotten the main code
.WRITE "xx"|[f]ausdruck[,...] ;prints either string "xx" or the result of the expression - without CR/LF at the end
;result can be formatted with preceding....
.write $val1,%val2,!val3 ;values are printed as hexadecimal, binary or simple decimal numbers
.write wval, bval ;hexadecimal output formatted as word or byte (=4/2 digits)
.write 3#val ;value shall be printed with 3 digits
.write -5#val ;5 digits but right-aligned
.WRITELN ;like .write but adding CR/LF
Macros and libraries:
Code:
<name> .BLK ;starts a block with local labels
.end <name> ;name must be identical to above - helps readability with longer blocks
or
.BLK <name>
.end <name>
In a block you can do
test .blk
#label1 ... ;this label will be visible in global scope
<label2 ... ;this label is visible in the surrounding block
.end test
.MAK [!]name [param,..] ;macro definition, is invoced with name or name paramlist or name (poaramlist)
...code ;in the code parameters are substituted
.END
.mak plot xval,yval
ldx #xval
ldy #yval
jsr plotfunc
.end
plot 123,myvalue ;this will result in:
.blk
ldx #123
ldy #myvalue
jsr plotfunc
.end
plot 123,!myvalue ;here myvalue will be evaluated first - e.g. for counters that change their value constantly
.mak !hallo ... ;the macro generates no local labels, i.e. labels are visible in the next higher block
.LIB <name> ;a library function which is only incorporated if used by main code, usage is evaluated at the end of pass 1, code is placed there if used.
.end <name>
.lib testlib
...
#entry ... ;this defines an additional entry point, so you can access the libfn not only at the beginning
...
.end testlib
.USES lib-fn ;markls a library function as used
Conditional code:
Code:
.IF expression ;if expression<>0 then assemble next block
...code...
.ELSE ;if expression=0 assemble this block
...code...
.ENDIF
or alternatively as single-line .if:
.IF expression code ;no else statement and cannot be mixed with .if-style above
.FOR [name=]startval,endval[,step=1] ; nomen est omen
...code... ;in the code block, name will be replaced by the for's value
.END
.for @i=1,3
.byte @i
.end
=>
.byte 1
.byte 2
.byte 3
.for @i=1,3,2 ; you can add a step value
.byte @i
.end
=>
.byte 1
.byte 3
.foreach name1,[name2...] ;the line of code at the end will be repeated with every element of the list
elem1
elem2
...
.end .byte name1[,name2]
.foreach vector
reset
$ffd2
fancy+expression*here
.end .word vector
=>
.word reset
.word $ffd2
.word <whatever result this expression has>
And finally segments:
Code:
.SEGMENT <seg> ;different segments of code like code, data, variables,...
.end <seg>
.segment init ;(or exit)
...
.end init ;=> segment for init (or clear-up) code, allows libraries to incorporate stuff for fire-up and shut-down. The segment is placed after the library code.
.segment data ;data with fixed value, will be incorporated in the file after lib/init/exit right before end of object file
.segment var ;data without fixed value, includes .cod - = generates no code just address space after the end of object file (if no *= is given)
Projects for the future:
Temporary label assignment - so routines can request temp space without me keeping track of what space is used/free
more CPU types - but as I have no need at the moment, this might take
built-in "optimization": assembler generates BNE xxx or BEQ yy JMP xx if relative jump is possible or not