This document is the style guide for the Bedrock assembler language.
Modules
The source code for a program is split across one or more text files, called modules. (if a program uses only one text file, that file is still called a module). Modules use the file extension .brc.
Each module is a piece of the final program: the modules are concatenated in a specific order to form the complete source file.
Module names
The name of a module should be written in kebab-case, with words written lower-case and separated by hyphens.
main.brc lib-string.brc player-2-controls.brc
The module that acts as the entry point to the program should be called main.brc.
Line length
The maximum length of any line in a module should be 80 characters.
If a line would exceed this length, it should be broken at the piece of whitespace closest to the limit, with the indentation of the new line matching the original line.
Multi-line comments should be indented so that the first characters on each line are vertically aligned. This means that the second and following lines should be indented by two extra spaces.
( Register a draw callback for the nth user interface element. This callback
will be called when the screen is entered, resized, or when the element is
interacted with. )
Line endings
Each line in a module should end with the single 0A byte (the ‘new line’ or ‘line feed’ character), also known as LF line endings. This is in constrast to CRLF line endings, where each line ends with the two bytes 0D 0A.
Each module should end with a single blank line.
Blank lines
Functions in a module should be separated from each other by a single blank line. If a module contains many functions, groups of related functions can be separated from other groups with two blank lines.
( Print a version string to the terminal. ) @print-version *:string/version TAIL:print-string ( Quit the program after cleaning up resources. ) @quit ( -- ) CALL:cleanup HLT
Trailing whitespace
Trailing whitespace on any line in a module should be avoided.
Values
Literal values
Hexadecimal literals should be written using uppercase letters.
@sprite 78 FC B4 FC B4 CC 78 00
Immediate values
An immediate value is a value that follows an immediate-mode instruction. There should be no whitespace between the instruction and the value.
*:callback-table ADD*:0004 LDA* CALL
Variables
Variables can be implemented by reading and writing a value from program memory. A local label should be used as a pointer to the value, and either padding syntax or a literal value should be used to create a placeholder for the variable. The placeholders for a group of variables should be vertically aligned, with at least two spaces placed between each label and value.
&x #02 &y #02 &dir #01
A more efficient type of variable can be implemented by overwriting the value used by an immediate-mode instruction. For this type of variable, a local label should be used as a pointer to the value, padding syntax or a literal value should be used to create a placeholder for the variable, and both the label and placeholder should be wrapped with marker brackets to make it clear that together they represent a single value. The label and placeholder should be separated by a single space.
:[&var #01]
Inline data
Blocks of data that will be used in only one place in the program (such as a single-use sprite or string) should be defined inline directly before the code that uses it.
The JMSr: instruction used with the {} block syntax will push the address of a block of data onto the working stack, and then will jump to the end of the block. A macro can be used to make the intention clearer.
%λ: JMSr:; λ:{"This is a string."} CALL:print-string HLT
Abbreviated instructions
The abbreviated variants of the push macros should be preferred over the verbose variants (use : instead of PSH:, and so on).
An exception to this rule is when you are using the instruction value as data, such as to push an instruction value onto a stack. In this case, the verbose variant should be used, surrounding it with marker brackets if the instruction is used as an immediate value.
:01 :02 :[PSH]
Functions
A function is a block of code that can be called from elsewhere to perform a particular behaviour. Other programming environments might call this a routine or a procedure.
A function begins with a global label, which is followed by a block of code that eventually returns. A documentation comment directly preceding the label describes what the function does and any notes any special considerations. A stack signature comment directly following the label describes the effect of the function on the stacks.
( Add 5 to a byte. ) @add-5 ( x -- x ) ADD:05 RETURN
Label names
The names of global and local labels should be written in kebab-case, with words written lower-case and separated by hyphens.
@quit ( ... ) @count-ones ( ... ) @draw-1-bit-sprite ( ... )
A leading hyphen on a global label indicates a function that shouldn’t be called directly. This is normally used for ‘inner functions’ that contain logic shared by other functions, where those other functions act as safe entry points for the inner function (handling set-up and tear-down as required). This is known as a ‘private function’ in other languages. Local labels should not be prefixed, they are assumed to be private by default.
@draw-1-bit-sprite ( ... ) @draw-2-bit-sprite ( ... ) @-draw-sprite-inner ( ... )
A trailing asterisk on a label indicates a variant of a function that operates on doubles instead of bytes. This should only be used for functions that operate on raw numeric values, as opposed to functions where the double represents an address, index, or other type.
@sort-ascending ( a b -- a b ) @sort-ascending* ( a* b* -- a* b* )
Indentation
A line of code is indented by inserting two additional spaces at the start of the line. Tabs should not be used for indentation.
The lines of code following a global or local label should be indented one level deeper than the label. This helps to indicate the extent of a function or loop.
@print-string ( addr* -- ) &loop DUP* LDA STD:86 INC* DUP* LDA JCN:~loop POP* JMPr
Documentation comments
A documentation comment is a comment that summarises the high-level behaviour of a function and any special considerations. Documentation comments should be written in the imperative mood and use proper punctuation (leading capitals, full stops).
Each function should be preceded by a documentation comment.
( Write a null-terminated string to a device port, including the null terminator. The string must be at least one character long. ) @write-string-to-device ( ... )
Stack signatures
A stack signature is a comment that summarises the values on a stack before and after a piece of code is run.
Each function should have a stack signature comment directly following the function label, with exactly two spaces separating the label from the comment.
@byte-to-chars ( byte -- char char ) ( ... ) @get-string-length ( addr* -- len ) ( ... ) @is-positive* ( value* -- pos? ) ( ... ) @push-to-return-stack ( a -- ) ( -- a )
Each letter or word in the signature represents a value on the stack. A trailing asterisk indicates a double, a lack of asterisk indicates a byte. A trailing question mark indicates a truth value, either 00 or FF.
The double hyphen separates the state from before the code is run from the state after the code is run. The value on the right end of each state is on top of the stack, with values moving down the stack as they move to the left. For example, the comment ( x y -- r* ) indicates that the stack starts with a byte y at the top and the byte x just below, and ends with both values removed and replaced with the double r*.
The stack signature represents the working stack. If the piece of code acts on both stacks, add a second stack signature to represent the return stack, separated from the first signature by a single space.
@push-byte ( x -- ) ( -- x ) PSHr ROTr ROTr RETURN
Stack annotations
Stack signatures can also be used to annotate the stack effects of a line of code. These annotations should be placed to the right of the line, with the signature indicating the stack state after the line is run. Omit the ‘before’ portion of the signature and the double-hyphen, keeping just the ‘after’ portion.
@hex-digit-to-ascii ( digit -- ascii ) DUP GTH:09 ( digit letter? ) AND:07 ADD:30 ( digit offset ) ADD RETURN ( digit )
The parentheses of all annotations in a block of code should be aligned vertically by adding spaces before each parenthesis. A minimum of two spaces should separate the code from the annotation.
Inline comments
An inline comment is a comment that explains the purpose of the following block of code. Inline comments should match the indentation of the following line, and should be written in the imperative mood and use proper punctuation (leading capitals, full stops).
@print-string ( addr* -- ) &loop ( Write a single byte to the stream device. ) DUP* LDA STD:86 INC* ( Check if the next character is the null terminator. If it is, the program will not jump and the loop will end. ) DUP* LDA JCN:~loop POP JMPr
Macros
A macro is a block of code that will be copied into multiple places in the program when assembled. Macros should be used for pieces of code that are used often but are too small to justify the overhead of a function call.
The macro name should be separated from the macro body by exactly two spaces. The macro body should not be separated from the macro terminator. Multi-line macros should be avoided.
%NEG DUP LTH:80 AND;
Macro names
The name of a macro should be written in UPPER-KEBAB-CASE, with words written upper case and separated by hyphens.
%MOD4 (...); %FILL-BG (...); %FILE-READ-NAME (...);
A trailing asterisk on a macro name indicates a variant of a macro that operates on doubles instead of bytes. This should only be used for macros that operate on raw numeric values, as opposed to macros where the double represents an address, index, or other type.
%NEG ( x -- x ) %NEG* ( x* -- x* )
A trailing question mark on a macro name indicates a macro that pushes a truth value to a stack, either 00 or FF.
%POS? ( x -- t? ) %POS?* ( x* -- t? )