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.