Expand description
§Advanced Subleq Assembler
This Subleq assembler assembles a custom language, called Sublang, into Subleq
§Features
- Interpreter and debugger
- Friendly and detailed assembler feedback
- Powerful macros
- Syntax sugar for common constructs like dereferencing
- Optional typing system
- Fully fledged standard lib including functions and high level control flow constructs like If or While
- Fine grained control over your code and the assembler
- Module and inclusion system
- 16-bit
- Extensive documentation
§What is Subleq?
Subleq or SUBtract and jump if Less than or EQual to zero is an assembly language that has only the SUBLEQ instruction, which has three operands: A, B, C.
The value at memory address A is subtracted from the value at address B. If the resulting number is less than or equal to zero, a jump takes place to address C. Otherwise the next instruction is executed.
Since there is only one instruction, the assembly does not contain opcodes.
So:
SUBLEQ 1 2 3
would just be
1 2 3
A very basic subleq interpreter written in Python would look as follows
while True:
a = mem[pc]
b = mem[pc + 1]
c = mem[pc + 2]
result = mem[b] - mem[a]
mem[b] = result
if result <= 0:
pc = c
else:
pc += 3Most subleq implementations, this one included, also include the IO operations: INPUT, OUTPUT and HALT.
These can be achieved by respectively having A = -1, B = -1 and C = -1. INPUT and OUTPUT read or write singular ASCII characters
§Installation
cargo install asa or download the binary from the releases tab
§Usage
asa MySubleq.sbl§Syntax highlighting
See https://github.com/Kat9-123/sublang-highlighting
§Project status
This project is functionally complete, but the documentation for the assembler, Sublang and Sublib is still lacking. As is the Sublib itself, which isn’t finished yet
§
It is best to view Introduction to sublang the following on https://github.com/Kat9-123/asa/blob/master/Sublang.md so you get at least a modicum of syntax highlighting
§Introduction to Sublang
Sublang is a bare bones assembly languages consisting of four main elements:
- The SUBLEQ instruction
- Labels
- Macros
- Syntax sugar
§Subleq
1 2 3 ; This is interpreted as standard subleq: mem[2] -= mem[1] jump to 3 if LEQ otherwise it goes to the next instruction.
; This syntax is valid, but pointless.
; memory addresses and instructions may be labeled
a -> 1
b -> 2
c ->
a -= b ; SUBLEQ: Subtract b from a
a -= b c ; Subtract b from a and jump to c if the result is less than or equal to zero
; If no `c` argument is given, the next instruction will always be executed, even if the result is LEQ
; So these two are equivalent
a -= b
a -= b $1 ; `$1` gives a relative address with offset one
a -= b ; is equivalent to
b a $1 ; This syntax works but is not recommended, since it makes it harder for the assembler to give hints
; Other examples, literals and labels may freely be combined
a -= b 0x0000
'\0' -= 0 c§Labels and Literals
a -> 123 ; Decimal
b -> 0x4C6 ; Hex
c -> "Hello, World!" ; Strings are null-terminated by the assembler
d -> 'P' ; Character literals
.label ->
a -= b .label ; Repeats as long as (a -= b) <= 0§Scopes
Scoping works like in most other languages. Note: Only labels are affected by scopes, macro definitions in scopes will still be globally accessible
Z -> 123
X -> 456
Y -> 0
{
Z -> 789
{
X -= Z ; 456 - 789
}
}
Y -= Z ; 0 - 123§IO
char -> 'a'
input -> 0
W -> 0
-1 -= char ; Prints 'a' to the screen
input -= -1
-1 -= input ; Echoes back users input
W -= W -1 ; Halts execution
§Types
The assembler has a simple type-checker, which can be disabled.
valuenormal labell_valueliteral values_valuescoped valuea_valueanything, no type checkingb_valuea braced valuem_valuea macro call passed as argument, must be braced. In practice it’s the same asb_valueCurrently types are only checked for macro parameters.
§Naming conventions
@MyMacromacros in CamelCasemy_labellabels in snake_case, with the exception of single character ‘registers’, likeZorWMyModule.sblmodules (files) are in CamelCase
§Labels
p_valuepointer (not type-checked)p_p_valuepointer to pointer (not type-checked)n_valuenegated value (not type-checked)value?macro argument in definition.valuelabel to jump toCONST_VALUEconstant, can be applied to all of the above and should be applied to macro arguments, but NOT to literals (l_name), since they are always constant by definitionModule::Valueor!Module::MacronamespacingModule::SubModule::value
§Macros
§Definition
@Name {
...
}
@Name ; This is allowed as well, but discouraged
{
...
}
; with parameters
@Name a? b? c? {
...
}
; It is also possible to define a macro that isn't scoped:
@Name a? [
...
]
; This, however, is dangerous when label definitions take place in the macro, so it is generally discouraged.
; Linebreaks are allowed between parameters
@Name a?
b?
c?
d?
e? {
...
}
§Expanding
!Name
; With arguments
!Name2 a b c
; Linebreaks are allowed between arguments§Hygiene
Macros are hygienic. Variables won’t be shadowed.
; Macros
a -> 0
@MyMacro b {
a -= b
a -> 123
}
!MyMacro a
; Is completely fine, and will become the following:
{
?MyMacro?a -= a
?MyMacro?a -> 123
}§Macro arguments
You may pass scopes as macro arguments
!Mac s_my_scope? {
s_my_scope?
}
!Mac { a -= b }
; =>
{
{ a -= b }
}
; If a macro takes multiple scopes, they can be chained as follows:
!Mac {
...
} {
...
} {
...
}If you don’t want the argument to be surrounded by scopes, you can use braces
@Mac b_my_braced? {
b_my_braced?
}
!Mac ( a -= b )
; =>
{
a -= b
}This means that you can ‘curry’ macros (using that term loosely)
@Mac b_some_macro? {
b_some_macro? 10
b_some_macro? 3
}
@CurriedMacro l_a? l_b? {
l_a? -= l_b?
}
!Mac ( !CurriedMacro 5 )
; =>
{
{
5 -= 10
5 -= 3
}
}
§Pointers
§Referencing
To create a pointer to a value, the relative address syntax $1 must be used to get the address of the next token
ptr -> $1 0x1234 ; This takes up two words of memory, one for the pointer and one for the value
ptr -> $1 "String"
; or
ptr -> &'A'
ptr -> &"String"
ptr -> &123
; & is equivalent to $1§Dereferencing
; Generic sequence for dereferencing. The value that 'b' points to will be subtracted from 'a'
!Copy ptr b
a -= (b -> 0)
; If 'ptr' is constant the following is also legal. Note: ptr doesn't have to be constant but this syntax will give unexpected results if it isn't
a -= (b -> PTR)
; The '*' operator may also be used:
a -= *ptr
; This is effectively syntax sugar for
!Copy ptr b
a -= (b -> 0)
; (But in reality it is exactly equivalent to)
_ASM _ASM &1
*ID*ptr *ID*ptr &1
ptr _ASM &1
_ASM *ID*ptr &1
a -= (*ID*ptr -> 0)
; *ID*ptr is a safe and automatically generated name
Remember that because of how Subleq works, what are called ‘Labels’ here, are also just pointers! But since Subleq dereferences them, we can think of them as values. But keep in mind that literals require indirection a -= 10 doesn’t subtract 10 from a
§Inclusions
The # symbol may be used to include another .sbl file anywhere
#MySblFile.sbl
; You may leave out the .sbl extension:
#MySblFile
; You can also do this:
...
Z -= Z
P -= Z
#IncludeMe
!Macro P
...
; But of course beware of the contents of the included fileIf you want to create a module (a set of .sbl files in a folder) you must create a folder with the name of the module (for example ‘sublib’). And in that folder create a Lib.sbl file. Whenever the ‘sublib’ folder is imported, this is automatically resolved to ‘sublib/Lib.sbl’. In this .sbl file you may include any other files you might need. Includes are initially resolved relative to the file
that is including, and if the target isn’t found in the LIBS folder, defined using the -l command line argument.
; ./subleq/MyFile.sbl
#math/FastSqrtThe order in which files are checked is as follows. The first one that exists will be included.
./subleq/math/FastSqrt/Lib.sbl./subleq/math/FastSqrt.sblLIBS/math/FastSqrt/Lib.sblLIBS/math/FastSqrt.sbl
See subleq/libs/sublib for an example.
§Syntax sugar
§Mult operator
When the ‘*’ is placed before a literal ?? The previous token is repeated n times
label * 3 ; =>
label label label
0x123 * 0x4 ; =>
0x123 0x123 0x123 0x123
; mind that 3 * label will dereference `label`!§Dereference operator
§Style guide
Just make sure it looks good :), or follow the style of the Sublib
§Namespacing
The format Namespace::Macro or Namespace::label should be used. This is solely a naming convention and not enforced in any way. This means that module authors must decide what namespace their macros or labels should have. This is obviously bad design, but it is simple.
§Conclusion
For many more examples see the standard library, called Sublib
§Sublib
Sublib is the standard library. It has a range of very basic features (Prelude.sbl, IO.sbl and Symbols.sbl) to quite advanced ones like functions and control flow.
§Examples
§Basic
!Print p_string
Z -= Z -1 ; Jumping to -1 halts
; equivalent to !Halt
p_string -> &"Hello, World!\n"
Z -> 0 ; Temp register
N_ONE -> -1
**
Pure no dependency implementation
**
@Print P_STRING? {
; Copy the pointer into the local ptr
Z -= Z
Z -= P_STRING?
ptr -= Z
Z -= Z
.loop ->
char -= char ; Clear char
Z -= (ptr -> 0) ; Z -= *ptr
char -= Z .fin ; Flip the character, since it is negative, and jump if
; result is LEQ (i.e. finish if it is a NULL)
-1 -= char ; Writes the character to the screen
ptr -= N_ONE ; Increment the pointer
Z -= Z .loop ; Infinite loop
char -> 0 ; This point is never reached, so it is safe to define the
; label 'char' here. It is very important to keep in mind
; that, in this case the zero, will be put in memory in
; this exact place and, if execution crosses it, it will
; be interpreted as an instruction. To define values in between
; instructions, use the '=' operator
.fin ->
}§Sublib
; This is how Sublang could should be written, making extensive use of macros
!J .main ; Jump to main
#sublib
#sublib/Control
p_string -> &"Hello, Sublang!\n"
**
Or using the standard lib
**
@PrintStdLib P_STRING? {
!= p_local P_STRING? ; p_local = P_STRING?
!=0 char ; char = 0
!Loop {
!DerefAndCopy p_local char ; char = *p_local
!IfFalse char {
!Break
}
!IO -= char
!Inc p_local
}
}
.main -> {
!PrintStdLib p_string
!Halt
}
; Or you can just use one of the Print macros from sublib/IO
!J .main
#sublib
main -> {
IO::PrintLnLit "Hello, Sublib!"
!Halt
}Modules§
- args
- assembler
- codegen
- Generate a vec of executable words from a vector of tokens
- feedback
- Generates and prints friendly feedback messages for the user
- files
- Management of input and output files
- lexer
- Converts a string into a vector of tokens, resolving includes along the way
- mem_
view - parser
- Parses a vector of tokens
- runtimes
- symbols
- tokens
- utils