BitMagic The Complete Development Environment for the Commander X16.

C# Blocks

Where a traditional assembler gives you a macro language, BitMagic gives you C#. Any line in a template that isn’t an assembly line is C# that runs while the file is turned into assembly: loops that unroll code, tables worked out at build time, whole routines generated from their arguments. This is where most of BitMagic’s power is.

This page covers writing that C#: control flow over your code, splicing values into assembly lines, methods, and the header directives that pull in code.

Control flow

C# statements run as the file is walked. An assembly line inside a loop or an if is emitted once for each pass:

.proc clear
    ldx #0
.loop:
    for (var page = 0; page < 4; page++)
    {
        sta $0400 + @(page * 256), x
    }
    inx
    bne loop
    rts
.endproc

The for header and the braces are C#. The sta line is assembly, so it is emitted four times, with @(page * 256) replaced by 0, 256, 512 and 768.

var, if, method calls and LINQ all work. System, System.Linq, System.Collections, System.Collections.Generic and System.Threading.Tasks are already imported.

Values

@(expr) inside an assembly line splices in the value of a C# expression:

.const sprites 8
ldx #@(sprites - 1)

A line that is just @expr emits the string that expr evaluates to.

@() runs as C#, before the file reaches the compiler, so it only sees C# values. It doesn’t know about labels, or the byte operators <, > and ^ (@(<target) won’t compile). Write label arithmetic as plain assembly, with no @():

lda #<target        ; low byte of target's address, no @() needed
ldx #>target        ; high byte

To emit assembly from C#, call a method that writes it, such as one from the BM library, as a plain statement with no @:

    BM.Petscii("READY.")

Methods

Define methods in the file and call them. Assembly written inside a method lands wherever the method is called, so a call is a parameterised block of code:

void delay(int loops)
{
    ldx #@(loops)
.:
    dex
    bne -
}

    delay(200)
    delay(50)

Header directives

These sit at the top of the file, before the first opcode, . line or C#.

A template runs in two phases: a setup phase that handles these lines, then the body, which walks the rest of the file and produces the assembly.

library

library Namespace.Class;

Marks the file as a library: a bundle of methods for other files to import, with no body of its own. Importers name it by Class; Namespace keeps it distinct. Leave out the . and the whole name is the class, with the namespace taken from the filename. A file with no library line is a program, and its body runs.

import

import Name = "file";

Builds file as a template of its own and brings it in as Name; you then call Name.Method(...). The quotes are required; the closing ; is optional. file is found as an absolute path, then relative to the file doing the import, then by bare name in the Library folder next to the debugger. file is normally a library.

import BM = "BM.bmasm";

    BM.X16Header()

include

include "file.cs";

Adds a C# source file to the build, compiled together with the template so its types are in scope. The path is relative to the .bmasm file, and the closing ; is optional. It is ordinary C#, not a template: no Name. prefix and no @.

using

using Some.Namespace;

A C# using, added to the generated code. Recognised only in the header, so put it before any body line.

reference

reference Some.Assembly;

Adds a .NET assembly to the build, loaded by name from what the runtime can already resolve. Follow it with a using for the namespaces you need. The name is not quoted.

assembly

assembly "path/to/lib.dll";

Like reference, but loads the assembly from a file. The path is tried against the project base path, then next to the debugger, then as written.

nuget

nuget "Package.Id", "1.2.3";

Downloads a NuGet package and adds its assemblies to the build, as assembly does for each .dll. The version is optional.

The ! prefix

!directive

Runs .directive in the setup phase, before anything the body emits. It prepends . to whatever follows, so any compiler directive works, from anywhere in the file. It does nothing in a library. Its main use is setting up segment layout ahead of the body.

Writing a library

A library file is a library line and a set of methods, with no body:

library MyProject.Screen;

public static void Fill(byte value)
{
    lda #@(value)
    ldx #0
.:
    sta $0400, x
    sta $0500, x
    sta $0600, x
    sta $0700, x
    inx
    bne -
}
import Screen = "screen.bmasm";

    Screen.Fill(0x20)

Libraries can import other libraries.