For the past few years, I've been tinkering with a custom version of the ACME assembler, mainly for my own use. I've been thinking about releasing it, but I thought I'd see if there was any interest before I put any more effort into documentation. Obviously, I'd also provide the source code, since ACME was released under the GNU GPL.
The project started as a modification of ACME 0.86, because I wanted to add support for token pasting in macros (ala C pre-processor macros). Over time, I back-ported many of the features from the later versions of ACME, including anonymous labels, floating-point math, and functions.
Eventually, I ported the whole thing to Java and added more features:
- Full support for Unicode source files, including characters outside of the BMP. Encoding may be specified in the first two lines of the file (via coding=...) or via BOM. Obviously, plain ASCII source files are still supported.
- Unicode data pseudo-opcodes: !utf8, !utf16le, !utf16be
- Floating-point data pseudo-opcodes: !fp16, !fp32, !fp64 (IEEE 754), and !fp40 (8-bit BASIC).
- Iteration over tokens or character sequences: !irp, !irpc
- Labels can hold string values (allowing strings to be passed to macros), with support for string slicing and concatenation.
- Can assemble more than 64KB of code via logical banks, each with independent output files.
- Full 24-bit program counter for 65816 code.
- Full compatibility with source written for the original C version of ACME
Perhaps the most sophistated feature is the macro processing engine. It supports regular expression matching for arguments, allowing the very syntax of the assembler itself to be augmented. For example, I was able to implement the entire Z80 instruction set as an importable macro package; and it doesn't require any mangling of the Z80 source code, other than adapting it to ACME's syntax for labels.
To illustrate, here is an example of a source file using the Z80 macro package (using a snippet from
http://www.andreadrian.de/oldcpu/Z80_number_cruncher.html. I did
not write this snippet of Z80 code):
Code:
!source "Z80.a" ; import Z80 macro package
*= $8000
Z80_ON ; enable Z80 instruction set
LD HL,01213H
LD DE,0F000H
EXX
LD HL,01011H
LD DE,0
EXX
CALL ADD32
HALT
ADD32
ADD HL,DE ; 16-BIT ADD OF HL AND DE
EXX
ADC HL,DE ; 16-BIT ADD OF HL AND DE WITH CARRY
EXX
RET
For those familiar with ACME, notice that macro calls no longer require the '+' symbol, and processor mnemonics can be overloaded by macros. For source compatibility, using '+' to call macros is still supported, however.
Here is another example of using the macro processor to alter the behavior of the assembler, this time to enforce MOS assembler "pedantic mode" for the bit-shift mnemonics:
Code:
; alter all bit-shift mnemonics
!irp @mnemo,{asl,lsr,rol,ror} {
; error if no argument supplied
!macro @mnemo* {
!error "@mnemo requires operand"
}
; accept 'a' or 'A' as an argument (accumulator mode)
!macro @mnemo* @?i="a" {
&@mnemo
}
; all other arguments
!macro @mnemo* # {
!outzone &@mnemo @#
}
}
asl ; this will now cause an error
asl a ; but this will work
asl $1000,x ; and this will still work, too
Another nifty feature (in my opinion anyway) are a set of !rtxx psuedo-opcodes which use the closest available RTS instruction to return, or else will emit a branch followed by an RTS instruction if one cannot be found. The assembler will even do a "pseudo pass" to find forward-referenced RTS instructions. For example, in the following snippet, the assembler would use the final RTS instruction if it was within branching range:
Code:
lda LIVES
!rtne ; return if not zero
; ... more code here
rts
; if final RTS in range, equivalent to:
lda LIVES
bne +
; ... more code here
+ rts
; otherwise, equivalent to:
lda LIVES
beq +
rts
+
; ... more code here
rts
One !rtxx pseudo-op can even use an RTS instruction generated by another. Thus, the assembler will attempt to generate the shortest possible code. Also, given the choice between two RTS instructions within range, where one would cause the branch to cross a page-boundary, the assembler will select the other.
Well, I hope I didn't ramble too much -- I didn't even list all the new features
. What do you think? Is there room for a Java-based ACME-compatible cross-assembler, or am I just muddying the waters?
Thanks,
Anthony Tolle