Skip to content
Lorenzi edited this page Dec 27, 2023 · 8 revisions

Code blocks

You can use braces {} to create code blocks with a sequence of expressions. You can create and assign to local variables using =. There's no need for expression separators, but you can use a comma , if needed. The last expression in the block is automatically returned.

For example:

#ruledef
{
    ; from the 6502 instruction set
    bpl {addr} =>
    {
        reladdr = addr - $ - 2
        0x10 @ reladdr`8
    }
}

Assert

Asserts can be used in a code block to validate and reject certain instruction arguments. If the condition given to assert() turns out to be false, an error is thrown.

For example:

#ruledef
{
    ; from the 6502 instruction set
    bpl {addr} =>
    {
        reladdr = addr - $ - 2
        assert(reladdr <=  0x7f)
        assert(reladdr >= !0x7f)
        0x10 @ reladdr`8
    }
}

You can also pass a custom message as the optional second argument, which will be shown on the error message in case the assert fails:

#ruledef
{
    ; from the 6502 instruction set
    bpl {addr} =>
    {
        reladdr = addr - $ - 2
        assert(reladdr <=  0x7f, "branch target is too far")
        assert(reladdr >= !0x7f, "branch target is too far")
        0x10 @ reladdr`8
    }
}

Multiple Match Resolution

When an instruction matches multiple rules, the assembler will select the rule which produces the fewest number of bits. You can use this to have automatic selection of the smallest binary representation for a given instruction. If multiple matching rules produce the same number of bits, the assembler will throw an error.

You can use assert() to guide the assembler on its selection. The assembler will safely disregard matching rules which fail assertions. If multiple rules still match, even after assertion filtering, the above-mentioned process kicks in, and the assembler will select the rule which produces the fewest number of bits.

For example, we can write:

#ruledef
{
    mov {value} =>
    {
        assert(value >= 0)
        assert(value <= 0xff)
        0x10 @ value`8
    }

    mov {value} =>
    {
        assert(value >= 0x100)
        assert(value <= 0xffff)
        0x11 @ value`16
    }

    mov {value} =>
    {
        assert(value >= 0x10000)
        assert(value <= 0xffffff)
        0x12 @ value`24
    }
}

If the arguments to the instruction cannot be resolved in the first pass, which can happen if you were using a label that will only be defined later, 0 or a value from the previous pass are used. Further passes will refine the label value until it stops changing, and the final form of the instruction is chosen.

asm Blocks

To reuse an instruction's binary code in another instruction, you can use asm blocks:

#ruledef
{
    mov {reg}, {value} => 0x11 @ reg`8 @ value`8
    zero {reg} => asm { mov {reg}, 0 }
}

In the above example, the zero instruction is really just an alias for moving zero into a register, so the instruction body just calls the other instruction with an asm block to avoid repetition.

Note that the asm block from the zero instruction has access to the reg argument, and passes its value on to the mov instruction. Before v0.13.0, you could access the reg value without using braces {}, but they're now required.

This works even if the argument was typed as a subrule instead:

#subruledef AorB
{
  a => 0xaa
  b => 0xbb
}

#ruledef
{
    mov {reg: AorB}, {value} => 0x11 @ reg`8 @ value`8
    zero {reg: AorB} => asm { mov {reg}, 0 }
}

The asm block works just like any other expression, so you can, for example, use concatenation:

#ruledef
{
    mov {reg}, {value} => 0x11 @ reg`8 @ value`8
    zero_all => asm { mov 0, 0 } @ asm { mov 1, 0 } @ asm { mov 2, 0 }
}

Or, equivalently, you can put multiple instructions inside a single asm block, one per line:

#ruledef
{
    mov {reg}, {value} => 0x11 @ reg`8 @ value`8
    zero_all => asm
    {
        mov 0, 0
        mov 1, 0
        mov 2, 0
    }
}