Style guide

This document is a style guide for the Bedrock assembler language.

Names

Module names

A module is a file with extension .brc, representing either a complete program or a part of a program that will be concatenated with other modules.

The name of a module should use kebab-case, with words written in lower-case and separated by hyphens.

main.brc
lib-string.brc
player-2-controls.brc

In a project comprising multiple modules, the module that acts as the entry point to the program should be called main.brc.

Label names

The name of a global or local label should use kebab-case, with words written in 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 shared logic for other functions, where those other functions act as safe entry points to the inner function (handling set-up and tear-down as required). This concept 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 function variant 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* )

Macro names

The name of a macro should use UPPER-KEBAB-CASE, with words written in upper case and separated by hyphens.

%MOD4  (...);
%FILL-BG  (...);
%FILE-READ-NAME  (...);

A trailing asterisk on a macro indicates a variant 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* )

Whitespace

Indentation

To increase the indentation of a line of code by one level, add two spaces to the start of the line. Tab characters should not be used for indentation.

The lines of code following a global or local label should be indented by one level, adding as labels are nested. 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

Line encoding

Each line should end with a single 0A byte (the ‘new line’ or ‘line feed’ character), instead of the pair of bytes 0D 0A. This is also known as LF line encoding, instead of CRLF encoding.

Blank lines

Functions should be separated by a single blank line. In a file containing many functions, groups of related functions can be separated from other groups with two blank lines instead.

( 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

A file should end with a single blank line (that is, the final byte of the file should be the new line character 0A).

Trailing whitespace

Trailing whitespace should be avoided.

Maximum line length

The maximum length of any line should be 80 characters. If a line exceeds this length, it should be broken at the nearest whitespace before the limit, with the new line matching the indentation of the original line.

( 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. )

Immediate values

An immediate-mode instruction should not be separated from the value that follows.

*:callback-table ADD*:0004 LDA* JMS

Comments

The start and end delimiters of a comment should be separated from the comment text by a single space.

( Add two values. )

The second and remaining lines of a multi-line comment should be indented such that the first character on each line aligns with the first comment character on the first line.

( 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. )

Functions definitions

The stack signature comment should be separated from the function label by two spaces.

@print-string  ( addr* -- )

Macro definitions

The body of a macro definition should be separated from the name of the macro by two spaces. The macro terminator should not be separated from the body.

%EXTEND  DUP GTH:7F SWP;

Multi-line macros should be avoided.

Comments

Documentation comments

A documentation comment is a long-form comment that summarises the functionality of a piece of code. It should describe high-level behaviour, such as a summary of behaviour or notes on data formats. Documentation comments should be written in the imperative tense, and use proper punctuation (leading capitals, full stops).

A documentation comment should be placed above every function, and at the top of every module.

( This module contains functions that manipulate and consume null-terminated
  strings. Strings are passed as the address of the first string character. )

( 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 signature comments

A stack signature comment is a comment that summarises the values on a stack before and after a piece of code runs. Every function should have a stack signature comment directly following the function label, with two spaces as a separator.

@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 represents a value on the stack. A trailing asterisk indicates a double, no asterisk indicates a byte. A trailing question mark indicates a truth value, either 00 or FF.

The double hyphen separator divides the stack state before the code is run from the state after the code is run. Values to the right of each half are at the top of the stack, and values move 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 x just below, and ends with both values removed and replaced with the double r*.

The signature represents the working stack by default. If two stacks signatures are present, the first stack will be the working stack and the second will be the return stack.

Stack annotations

Stack signature comments can be used to annotate complex sections of code. Each comment should be placed to the right of the annotated line, with the comment indicating the state at the end of that line. The ‘before’ half of the signature and the double-hyphen separator should be omitted.

@hex-digit-to-ascii  ( digit -- ascii )
  DUP GTH:09      ( digit letter? )
  AND:07 ADD:30   ( digit offset  )
  ADD RETURN      ( digit         )

The parentheses of all comments should align vertically, and every comment should be separated from the annotated code by at least two spaces.

Notes

Comments can be used to provide an explanation for a line or section of code. These comments should match the indentation of the line below, and should use capital letters and 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

Miscellaneous

Literal values

Hexadecimal literals should be written using uppercase letters.

@sprite 78 FC B4 FC B4 CC 78 00

Abbreviated push instructions

The abbreviated push macros should be used over the verbose push macros. An exception is when pushing the value of an instruction directly, in which case the verbose macro should be used along with marker brackets.

:01 :02 :[PSH]

Pushing multiple bytes

When pushing two bytes representing separate values to the stack, a double-mode push instruction should be used for efficiency. The bytes should be separated by once space to indicate that they are separate values, and the pair should be wrapped with marker brackets to indicate that they’ll be pushed by a single instruction.

*:[01 02]

Self-modifying code

When using self-modifying code to modify the value pushed by an immediate-mode instruction, a local label should be used as a pointer to the value, padding syntax should be used to create a placeholder for the value, and both label and padding should be wrapped with marker brackets to indicate that they are related.

:[&var #01]

Single-use data

A block of data (such as a string or sprite) that will only be used in one place in the program should be defined inline just before it will be used. The JMSr: instruction used with the block syntax will push the address of the data onto the working stack and then jump to the end of the block. A macro can be used to make the intention of the JMSr: instruction clearer.

%λ:  JMSr:;

λ:{"This is a string."} CALL:print-string