Aquarius DevStudio Cb Compiler - fvdhoef/aquarius-plus GitHub Wiki

Aquarius DevStudio - Cb Compiler

Aquarius DevStudio comes with a compiler for the Cb language (pronounced: C-flat).

The Cb programming language is a new programming language designed by Frank van den Hoef for the Aquarius+. The language is based on the C programming language, but is simplified to make it possible to run the compiler on actual Aquarius+ hardware.

It is not the aim of the compiler to produce the most optimized code, but it will generate code that is much faster than programs coded in BASIC. Furthermore, it is possible to mix Cb and assembler code in the same file to allow speed-critical parts of a program to be optimized in assembly.

The language is kept as simple as possible. There are no structs, unions, enums, do/while loops, etc.

The compiler will compile a .cb source file into a .asm file, which then can be compiled with the assembler. The output assembly file will also contain the lines from the source file as comment interleaved with the generated code, so that generated code can by easily inspected.

Variables

Two base types are supported:

  • char (unsigned 1-byte value)
  • int (signed 2-byte value)

Variable names should start with one of: _ a-z A-Z and can be followed by _ a-z A-Z 0-9.

When global variables and function names are emitted into the assembler output file they are prefixed by a _.

Local variables can be defined anywhere in a function.

Examples of various variable declarations:

char a;                     // Uninitialized character variable
char b = 42;                // Initialized character variable

int c;                      // Uninitialized integer variable
                            // Global variables are initialized to 0, local variables will
                            // have a random stack value and should always be assigned a value
                            // before using it.

int d = 10 + 3 * 2;         // Global variables can be initialized with a compile time expression
                            // Local variables can be initialized with any expression

char *scr = 0x3000;         // A pointer to memory location 0x3000

// Arrays (can only be defined at the global level)
int e[10] = {1,2,3,4};      // This will use 20 bytes of RAM of which the
                            // first 8 bytes are initialized by the values given

int f[] = {1,2,3};          // This will use 6 bytes of RAM

int g[] = "Test";           // This will use 5 bytes of RAM (of which 1 byte for zero-termination)

const int h = 1234;         // Const variables don't consume any RAM, but can only take a
                            // compile-time expression.

ioport IO_VCTRL = 0xE0;     // IO-port variables will emit a OUT or IN instruction with the given
                            // value as IO port (0xE0 in this case)

extern char mydata[];       // Useful to use data provided by assembly files. In this case with the label _mydata.

Constants

Kind Example
Decimal 1234
Hexadecimal 0xBEEF
Character 'A'

Character constants and string literals support the following escape sequences:

Sequence Description
\n 10 - newline
\r 13 - carriage return
\b 8 - backspace
\\ \
\' '
\" "

String literals

When a string literal is used, it is automatically emitted at the global scope and a label is assigned to it. The pointer of the label is then used in the expression.

puts("Hello world!\n");

char *p = "Test";

Functions

A function can be defined as follows:

myfunction1(int a, int b) {
    return a * b;
}

myfunction2() {
    int a = myfunction1(1, 2);

    // Do stuff
}

A return value is optional, but if used, is always a 16-bit signed value (int).

Keywords

if / else

Conditional code can be made using the if and else keywords.

if (a < 10) {
    // Do something
} else {
    // Do something else
}

for

For takes up to 3 expressions:

int a = 0;
for (a=0; a<10; a=a+1) {
    // Do things
}

Within a for loop, the continue statement can be used to directly go the next iteration of the loop and the break statement can be used to exit the loop.

while

While takes an expression that is evaluated. If the result is non-zero the code block is executed. Within a while loop, the continue statement can be used to directly go the next iteration of the loop and the break statement can be used to exit the loop.

int a = 0;
while (a < 10) {
    a = a + 1;
}

return

Return from a function with an optional return value. Functions will automatically return at the end of the function and don't require the return keyword for this.

Expressions

Complex expression are supported and follow the C precedence rules. The following operators are supported (listed in descending precedence):

Operator Description
() [] Function call, array subscripting
- + ~ ! * & Unary negate, Unary plus, Unary bitwise-not, Logical not, Dereference pointer, Address-of
(char *) (int *) Cast to character pointer, cast to integer pointer
* / % Multiplication, division, and remainder
+ - Addition and subtraction
<< >> Bitwise left shift and right shift
<= >= < > Relational operators: less-or-equal, greater-or-equal, less-than, greater-than
== != Equal (==), Not equal
& Bitwise AND
^ Bitwise XOR
| Bitwise OR
&& Logical AND
|| Logical OR
= Assign

When comparison operators are used with a pointer type the comparison is done unsigned, otherwise comparisons are done signed.

Computations are always done in 16-bits. Character variables are extended to 16-bit when accessed (zeroes in upper 8 bit). When a value is assigned to a character variable, the upper 8 bits are discarded.

Assignments are part of the expression and will have the value of the right hand-side of the assignment. This allows performing an assignment within an expression, for example:

if ((a = myfunc()) != 0) {
}

or

int a;
int b;

a = b = 5;

Directive: #asm / #endasm

The #asm directive can be used to directly insert assembly code into a Cb file. All lines that are enclosed between #asm and #endasm are output verbatim to the output assembler file.

Directive: #include

The #include directive can be used to include other Cb files into the current file. This way code can be split up over multiple files.

#include "other_file.cb"

Calling convention

When interfacing with assembler code it is necessary to know how the compiler will call the function. Arguments to a function are pushed from right to left onto the stack. Arguments are always pushed as 16-bit value even when passing a 8-bit value.

The return value of a function if returned in the HL register. The caller is responsible for cleaning the arguments from the stack after the functions returns.

The IX register should be preserved by the callee. This is used as the frame pointer.

Example putchar function, calling the Aquarius ROM entry point $1D94:

// putchar(char c)
#asm
_putchar:
    push    ix          ; Preserve IX
    ld      ix,0        ; IX=SP
    add     ix,sp
    ld      a,(ix+4)    ; Get variable 'c' from stack
    call    $1D94       ; Call ROM function
    ld      sp,ix       ; SP=IX
    pop     ix          ; Restore IX
    ret                 ; Return to caller
#endasm
⚠️ **GitHub.com Fallback** ⚠️