I would like to convert an assembly program in ca65 with macros into assembly code with resolved macros in order to carry out manual or automatic optimizations with the resulting code. Then, the program should be assembled into object code/executable code.

Is there an option or macro for ca65 to make it run only the macro pre-processor?

I'm assembling with

ca65 myprog.s
cl65 myprog.o -C c64-asm.cfg -u __EXEHDR__ -o myprog.prg

or using cl65 in one step

cl65 myprog.s -C c64-asm.cfg -u __EXEHDR__ -o myprog.prg

Mini example for myprog.s with macros:

loop:   inc $D021
        .repeat 20
        jmp loop
  • 1
    AFAIK CA65 does not provide an intermediate, macro resolved, form. Does simply parsing the listing not satisfy your need? Maybe with a high number of listed bytes per line?
    – Raffzahn
    Commented Dec 13, 2020 at 0:47
  • Thanks for the suggestion. Simply parsing would work for .repeat, but I also might have recursive macros and usage of .if, .ident', ̀set, etc. Parsing this would mean a reimplementation of the macro processor.
    – Peter B.
    Commented Dec 13, 2020 at 0:53
  • 1
    Erm, no, I thought about parsing the listing produced, not the source code.
    – Raffzahn
    Commented Dec 13, 2020 at 0:59
  • I generated the listing with ca65 --listing myprog-lst.txt myprog.s but the listing does not expand the macros to source code either. It shows the program bytes, but on the right just the macro name. It does not add labels generated by the macro for example, a BNE appears just as 06 in the text. Decoding the program bytes would be thus the same as running a disassembler over the generated binary, which is risky since the disassembler could potentially oversee a jump label or misinterpret code. In contrast, the assembler, getting the code from the macro'd sourcecode has this information.
    – Peter B.
    Commented Dec 13, 2020 at 1:23
  • 1
    True, still, it would be better than running a disassembler, as the interpretative part is only needed for macros. So results depend a lot on the amount and complexity of macros. But yeah, CA65 is, lets say unusual, as assembler. (P.S.: number of bytes listed can be handeled with --list-bytes)
    – Raffzahn
    Commented Dec 13, 2020 at 1:26

1 Answer 1


It appears the answer is no: ca65 is a single-pass assembler and unlike C and NASM, there is not a stand-alone preprocessing stage in the assembly process. Moreover, the design of some of the assembler’s features would make it pretty difficult to support:

  • The implementation of local symbols has the assembler internally re-write the identifiers into forms that cannot be represented in user code and parsed normally. This could be changed so that it generate normal identifiers, but it would then require ensuring that those not conflict with any already-declared ones (or, more inconveniently, any that might be declared later), which is currently unnecessary; this alone might considerably complicate the assembler.

  • .if directives can refer to symbols whose values may depend on code encountered before the symbol, which means that evaluating conditionals requires performing practically the full assembly process. Such conditionals might actually influence which macros are defined and with what contents. Consider how the macro expansion process might handle this code:

    .org 0
        ; nop ; uncomment to change the error message
    .if foo <> 0
        .macro quux
        .error "foo was not zero"
        .macro quux
        .error "foo was zero"
        ; trigger the error twice

At this point, you might as well perform the full assembly process and perform optimisation on object code (presumably with the help of the listing file which will tell you where instruction boundaries lie).


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .