7.3. Pseudo Commands

Pseudo commands are a special kind of macros that take command arguments, like #20, table,y or ($30),y as arguments just like mnemonics do. With these you can make your own extended commands. Here is an example of a mov command that moves a byte from one place to another:

.pseudocommand mov src:tar {
    lda src
    sta tar
}

You use the mov command like this:

mov #10 : $1000          // Sets $1000 to 10  (lda #10, sta $1000)
mov source : target      // target = source   (lda source, sta target)
mov source,x : target,y  // (lda source,x , sta target,y)
mov #20 : ($30),y        // (lda #20, sta ($30),y )

The arguments to a pseudo command are separated by colon and you can use any argument you would give to a mnemonic.

Note: In version 3.x, arguments where separated by semicolon. To make old code compile use the -pseudoc3x commandline option or convert the code with the 3.x to 4.x converter.

You can add an optional colon in front of the pseudocommand calls. This enables you to call a command with the same name as a mnemonic.

.pseudocommand adc arg1 : arg2 : tar {
    lda arg1
    adc arg2
    sta tar
}

adc #$10                 // This calls the standard mnemonic       
:adc #$20 : $10 : $20    // This calls the pseudocommand

The command arguments are passed to the pseudo command as CmdValues. These are values that contain an argument type and a number value. You access these by their getter functions. Here is a table of the functions:

Table 7.1. CmdValue Functions

Function Description Example
getType() Returns a type constant (See the table below for possibilities). #20 will return AT_IMMEDIATE.
getValue() Returns the value. #20 will return 20.

The argument type constants are the following:

Table 7.2. Argument Type Constants

Constant Example
AT_ABSOLUTE $1000
AT_ABSOLUTEX $1000,x
AT_ABSOLUTEY $1000,y
AT_IMMEDIATE #10
AT_INDIRECT ($1000)
AT_IZEROPAGEX ($10,x)
AT_IZEROPAGEY ($10),y
AT_NONE  

Some addressing modes, like absolute zeropage and relative, are missing from the above table. This is because the assembler automatically detect when these should be used from the corresponding absolute mode.

You can construct new command arguments with the CmdArgument function. If you want to construct a new immediate argument with the value 100, you do it like this:

.var myArgument = CmdArgument(AT_IMMEDIATE, 100)
lda myArgument   // Gives lda #100

Now let’s use the above functionalities to define a 16 bit instruction set. We start by defining a function that given the first argument will return the next in a 16 bit instruction.

.function _16bitnextArgument(arg) {
    .if (arg.getType()==AT_IMMEDIATE) 
        .return CmdArgument(arg.getType(),>arg.getValue())
    .return CmdArgument(arg.getType(),arg.getValue()+1)
}

We always return an argument of the same type as the original. If it's an immediate argument we set the value to be the high byte of the original value, otherwise we just increment it by 1. This will supply the correct argument for the ABSOLUTE, ABSOLUTEX, ABSOLUTEY and IMMEDIATE addressing modes. With this we can easily define some 16 bits commands:

.pseudocommand inc16 arg {
    inc arg
    bne over
    inc _16bitnextArgument(arg)
over:
}

.pseudocommand mov16 src:tar {
    lda src
    sta tar
    lda _16bitnextArgument(src)
    sta _16bitnextArgument(tar)
}

.pseudocommand add16 arg1 : arg2 : tar {
    .if (tar.getType()==AT_NONE) .eval tar=arg1
    clc
    lda arg1
    adc arg2
    sta tar
    lda _16bitnextArgument(arg1)
    adc _16bitnextArgument(arg2)
    sta _16bitnextArgument(tar)
}

You can use these like this:

inc16 counter
mov16 #irq1 : $0314 
mov16 #startAddress : $30
add16 $30 : #128 
add16 $30 : #$1000: $32

Note how the target argument of the add16 command can be left out. When this is the case an argument with type AT_NONE is passed to the pseudo command and the first argument is then used as target.

With the pseudo command directive you can define your own extended instruction libraries, which can speed up some of the more trivial tasks of programming.