Libraries
A library is a file other files import. This page covers writing one in plain bmasm, with no C#, and choosing where its code ends up in your program.
A library splits its code into named sections, such as setup for declarations and code for procedures. Each section is written wherever the program places it. If you don’t place it, it goes in a sensible default spot. You can make your own sections, decide how they’re written, and even define new kinds of file.
Write a library in plain bmasm
Start the file with file library, then put each part of the library under a section line:
; text.bmasm
file library
section setup
.scope text
.const RETURN 13
.endscope
section code
.scope text
.proc print ; prints the string at X (low) and Y (high)
...
lda #RETURN
jmp BSOUT
.endproc
.endscope
file librarymust be the first line that isn’t blank or a comment.sectionhas no.. Likeimport, it’s read by the template engine, a level above the compiler.- Every line of asm must be in a section. Asm before the first
sectionis an error, reported at that line. - A section can appear more than once in a file. Each
sectionline just switches which section the following lines go into. - Template code,
@(...)and C# loops, still works anywhere in the file.
A library with only one section can name it on the file line instead:
; text.bmasm
file library code ; everything is in code until a 'section' line
.scope text
.proc print
...
.endproc
.endscope
Use a library
Import it as usual:
; main.bmasm
import BM="bm.bmasm";
import Text="text.bmasm";
BM.X16Header();
ldx #<hello
ldy #>hello
jsr text:print ; text's code is written after main's
rts
With nothing else in main, the program comes out in this order:
every library's setup
main
every library's code
setup is written before BM.X16Header(), so keep it to declarations: segments, variables and constants. Code there would land ahead of the BASIC stub.
A library imported by several files is only written once. A library that only another library imports is still written: every library’s Initialise() runs once, after the libraries it imports.
Choose where sections go
place <section> writes a section at that point. It works in any file, not only main:
import BM="bm.bmasm";
import Text="text.bmasm";
BM.X16Header();
place setup ; every library's setup, after the BASIC stub
jsr text:print
rts
.segment high_code
place code ; every library's code, in this segment
.endsegment
A section you place isn’t placed again by the default. Writing place code twice is an error.
Place one library’s code
Different libraries often belong in different places, say DOS code in a RAM bank and decompression code in the main program. place <alias>.<section> places one library’s part of a section, where the alias is the name it was imported as:
import BM="bm.bmasm";
import Dos="dos.bmasm";
import Lz="decompress.bmasm";
BM.X16Header();
...
.segment dos_bank ; RAM bank 1
place Dos.code ; only dos.bmasm's code
.endsegment
place code ; everyone else's code, decompress.bmasm's included
place codetakes every part ofcodethat isn’t placed anywhere else. The order of theplacelines doesn’t matter.- If nothing writes
place code, whatever is left still goes in the default spot. - To place a library you don’t use directly, such as one another library imports, import it as well to give it an alias. Imports are only built once, so this costs nothing.
Make your own section
Any name works. Only setup and code have a default spot, so any other section must be placed:
; music.bmasm
file library
section irq
jsr music:tick ; run every frame
section code
.scope music
.proc tick
...
rts
.endproc
.endscope
; main.bmasm
.proc irq_handler
place irq ; jsr music:tick, and every other library's irq code
jmp (old_irq)
.endproc
If nothing places it, the build fails and names the libraries whose code would be lost:
Section 'irq' has code from 'music.bmasm' that is never placed. Add 'place irq', or 'place <alias>.irq' for one library.
Change how a section is written
By default, placing a section writes each library’s part in turn. A library can replace that by setting the section’s Writer, which is called once for each place the section is written, with the parts placed there:
; irqchain.bmasm
library MyGame.IrqChain;
private static void WriteIrq(IReadOnlyList<SectionPart> parts)
{
.proc irq_handler
foreach (var part in parts)
{
part.Write(); // each library's irq code, in turn
}
jmp (old_irq)
.endproc
}
public override void Initialise()
{
Section.Get("irq").Writer = WriteIrq;
}
Now any file that imports irqchain.bmasm gets the whole handler from a single place irq.
Add sections from a C# library
A library with C# helpers keeps the library Namespace.Class; form, and adds its sections from Initialise(). Owner is the library’s source file:
library MyGame.Window;
private static void Setup()
{
.segment window_data, scope=window
.padvar byte count
.endsegment
}
private static void Code()
{
.scope window
.proc draw
...
.endproc
.endscope
}
public static void OutputLine() { ... } // a C# helper, as before
public override void Initialise()
{
Section.Get("setup").Add(Owner, Setup);
Section.Get("code").Add(Owner, Code);
}
Initialise() only registers sections, so keep it free of output: calling Setup() directly would write the code straight away, where place can’t move it. Do any C# set up that main relies on there too, such as registering a callback that main reads while it runs, since section code is only written after main has run.
A library only registers its own code. Every library it imports is initialised as well, so registering an imported library’s code yourself would write it twice.
Write your own kind of file
file <kind> hands the file to a view, an ordinary library called <kind>.bmasm. It’s found like an import: next to the file, then in BitMagic’s own library folder. file library is just the view library.bmasm that ships with BitMagic, written with the same API as any other view.
A view needs a Process(ViewFile file) method. It can also claim keywords of its own, which then work like section does in a library:
; data.bmasm: the view behind 'file data'
library MyGame.DataView;
public static IEnumerable<string> Keywords => new[] { "bank" };
public static void Process(ViewFile file)
{
var bank = file.Arguments ?? "0"; // 'file data 3' starts in bank 3
var items = new List<CapturedItem>();
foreach (var item in file.Capture())
{
if (item is KeywordLine { Keyword: "bank" } keyword)
{
bank = keyword.Arguments; // 'bank 4'
continue;
}
items.Add(item);
}
Section.Get("data").Add(file.Owner, () =>
{
.segment @($"data_bank_{bank}")
Template.Write(items);
.endsegment
});
}
; levels.bmasm
file data 3
.level1:
.byte 1, 2, 3
.level2:
.byte 4, 5, 6
Anything after file <kind> is passed to the view untouched, as file.Arguments.
What the build checks
Every part of every section is tracked, so code is never written twice and never silently dropped:
| Mistake | Error |
|---|---|
Asm before the first section |
'lda #1' is before the first 'section'. Every line in a 'file library' file must be in a section, or name a starting section: 'file library code'. |
place code twice |
'place code' is already used at main.bmasm(31). |
place Dos.code twice |
'dos.bmasm' code is already placed at main.bmasm(12). |
| An alias that isn’t imported | 'Dso' isn't an import in this file. |
| A section the library doesn’t have | 'dos.bmasm' has no 'cod' section. |
| A section nothing places | Section 'irq' has code from 'music.bmasm' that is never placed. ... |
A file file listed as the program |
A 'file library' file can't be built on its own. Import it from the main file. |
The section API
These live in BitMagic.TemplateEngine.Objects, which every template can already use.
| Name | Type | Optional | Description |
|---|---|---|---|
Section.Get(name) |
Section |
No | The section with that name, created on first use. |
section.Add(owner, write) |
method | No | Adds a library’s code. A second call for the same owner is ignored. |
section.Place() |
method | No | Places every part not placed by library, here. |
section.Place(file, line, owner) |
method | No | Places one library’s part, here. |
section.PlaceAt(anchor, ...) |
method | No | As Place, at an anchor rather than here. |
section.Writer |
Action<IReadOnlyList<SectionPart>> |
Yes | How a placement is written. Defaults to writing each part in turn. |
section.RestPlaced |
bool |
No | Whether place <section> has been used. |
Build.OnStart(action) |
method | No | Runs after every library’s Initialise(), before main. |
Build.OnEnd(action) |
method | No | Runs after main, before the sections are written. |
Template.Anchor() |
Anchor |
No | Marks the current spot in the output, to be written to later. |
Template.WriteTo(anchor) |
IDisposable |
No | Sends the output to the anchor until disposed. |
Template.Capture(body) |
IReadOnlyList<CapturedItem> |
No | Runs body and returns what it wrote. |
Template.Write(items) |
method | No | Writes captured items, keeping their source lines for errors and the debugger. |
ViewException(item, message) |
exception | No | An error reported at a line of the viewed file. |