This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Bramble Reference

Reference documentation for the Bramble Language and the Compiler

This provides reference documentation for the Bramble language and for the compiler.

Within this section will be clear and explicit documentation covering the features of the Bramble language, including syntax rules, semantic rules, and so on.

1 - Keywords

Bramble Language Keywords

Operators

Name Description
null Represents a null reference or address. A reference that does not point to any location in memory

null

null represents a raw pointer value that does not point to any location in memory. This is used to initialize a raw pointer with a placeholder value, when it is not yet known where the pointer should point.

null is also used to check if a pointer points to a valid location in memory or not. For managing heap allocated memory, null can be used to check if memory has been allocated on the heap or not.

null can be used for initialization, mutation, or comparison operators, but not for any other operation.

2 - Operators

Bramble Operators

Operators

Operator Description
^ Dereference raw pointer
==, != Equal to and Not Equal to
<, <=, >, >= Comparison operators
@const Address Of immutable addressable expression
@mut Address Of mutable addressable expression
@ Raw pointer offset

== & !=

Equality Operations

== checks if the value on the left and the value on the right are equal. != checks if the value on the left and the value on the right are not equal.

For raw pointers, this compares the memory addresses stored in the pointers rather than the values stored at the memory address.

Syntax

EQUAL := EXPRESSION == EXPRESSION
NOT_EQUAL := EXPRESSION != EXPRESSION

Semantics

x, y: T, T: Primitive :- x == y -> bool
x, y: T, T: Primitive :- x != y -> bool

<, <=, >, & >=

Comparison Operations

These are the ordering operations and compare two values to see what relative order they are too each other.

For raw pointers, these compare the memory addresses stored in the pointer rather than the values stored at the memory address.

Syntax

LT := EXPRESSION < EXPRESSION
LTE := EXPRESSION <= EXPRESSION
GT := EXPRESSION > EXPRESSION
GTE := EXPRESSION >= EXPRESSION

Semantics

x, y: T, T: Primitive :- x < y -> bool
x, y: T, T: Primitive :- x <= y -> bool
x, y: T, T: Primitive :- x > y -> bool
x, y: T, T: Primitive :- x >= y -> bool

@const & @mut

Raw Pointer Creation

The Address Of operators return a raw pointer to the location in memory where the value of the operand is stored. The operand for @const and @mut must be an addressable expression.

An addressable expression is any expression which is logically tied to a location in memory and is not a temporary variable used for transferring data between calls. An example of an addressable expression is a named variable, or the element of an array which is bound to a variable. An addressable expression may be stored in the stack or in the heap.

An example of a non-addressable expression, which may have a location in memory is the temporary value of a structure expression: MyStruct{ x: 1, y: 2, z: 3}. This may be allocated to the stack to because it is too big to fit in a register, but that location is considered strictly temporary and may get used for later structure expressions, or, for small structures, be optimized into a physical register or integer literal. Therefore, there can be no guarantee that it can be safely addressed and it is considered non-addressable.

Note about Mutability

It’s important to remember that there are two different concepts of mutability for a value of type pointer. The first, and most important, is the mut in *mut T: this means that the location the pointer points to can be mutated through the pointer. The second is the mut that refers to a variable of type *(const|mut) T (let mut p: *const i64 ...), this means that the pointer variable can be mutated (not the location it points to); in other words, that the pointer can be changed to point do a different address.

Example

  let i: i64 := 10;
  let mut pi: *const i64 := @const i;


  let mut j: i64 := 15;
  let pj: *mut i64 := @mut j;

  mut pi := @const j;   // Here, `pi` is mutated to point to `j`. 
                        // Note, that it still _cannot_ be used to mutate `j`

Syntax Rules

ADDRESS_OF := @(const|mut) EXPRESSION

The @ operator must be followed by either const or mut. This indicates whether to pointer can be used to write to the memory location or only to read from the memory location.

Semantic Rules

// x is an addressable value, T is a type
x: T :- @const x -> *const T
mut x: T :- @const x -> *const T, @mut x -> *mut T

@const can take any addressable value, whether it is mutable or immutable and will always return a value of *const T, where T is the type of the operand.

@mut can only be applied to an addressable value that is declared as mutable. And will return a value of type *mut T, where T is the type of the operand. This pointer can be used to write to the memory location as well as read from the memory location.

^

Raw Pointer Dereference operator

Taking a *const T or *mut T value as an operand, the ^ operator accesses the value stored in the location pointed to by the operand. If the location is mutable (operand is of type *mut T), then the dereference expression can be used on the left of a mutation expression. If T is an array or a structure, then the result of the dereference operation can be used as the left of element access or field access operators.

Example

  // Dereference a primitive value
  let i: i64 := 5;
  let p: *const i64 := @const i;
  project::std::io::writei64ln(^p);  // Prints 5

  // Using the dereference in a mutation expression
  let mut j: i64 := 10;
  let pj: *mut i64 := @mut j;
  mut ^pj := ^pj + 1;               // Adds 1 to j

  // Using with an array
  let arr: [i32; 2] := [1i32, 2i32];
  let p: *const [i32; 2] := @const arr;
  ^p[0]; // Will resolve to 1i32

Syntax Rules

DEREFERENCE := ^EXPRESSION

Semantic Rules

x: *const T :- ^x -> T
x: *mut T :- ^x -> mut T

@

Raw Pointer Offset

The @ operator is a binary operator and is used for computing address offsets from a raw pointer value. Given a pointer to a memory location, you can use @ to move to the left or the right of that memory location. The offsets are computed in increments of the size of the underlying type. So, if p is pointer to an i64, then p@1 would increment the address by 1*size_of(i64) or 8 bytes and p@-3 would decrement the address by -3*size_of(i64) or -24 bytes.

The left operand must be of type *(const|mut) T and the resulting type will be the same as the left operand. The right operand must be an integer type (either signed or unsigned).

Example:

    let mut arr: [i64; 3] := [1, 2, 3];
    let mut p: *const i64 := @const arr[1];

    project::std::io::writei64ln(^(p@-1)); // Will print 1
  
    let p2: *const i64 := p@1;
    project::std::io::writei64ln(^p2);  // Will print 3

Syntax Rules

RAW_POINTER_OFFSET := EXPRESSION @ EXPRESSION

Semantic Rules

x: *const T, o: i8|i16|i32|i64|u8|u16|u32|u64 :- x@o -> *const T
x: *mut T, o: i8|i16|i32|i64|u8|u16|u32|u64 :- x@o -> *mut T

3 - Type Casting

Type Casting

Operator

Name Description
as Allows primitive types to be cast between each other.

Casting

The as operator converts a value of one type to a value of another type. As much as possible, the actual value is preserved, but there are certain value ranges for which that cannot be done.

This can also be used to change the target type of a Raw Pointer value. For example, *const MyStruct can be changed to *const u8.

Valid Casts

Expression Target Type Description
Numerical Numerical Signed integers, unsigned integers, and floating point numbers can be cast between each other.
*mut T *const Y or *mut Y Mutable raw pointers can be cast to constant or mutable raw pointers
*mut T or *const T *const Y Any raw pointer can be cast to a constant raw pointer
*mut T or *const T integer Any raw pointer can be cast to an integer
Integer *const T An integer can be cast to a constant raw pointer

Numerical types: bool i8 i16 i32 i64 u8 u16 u32 u64 f64

Integer types: bool i8 i16 i32 i64 u8 u16 u32 u64

Floating point types: f64

T and Y can be any type.

For raw pointer casts, the goal is to make it as difficult as possible to bypass the declared mutability of a location in memory.

Integer Upcasting

When an integer is cast to an integer with higher bit width the additional bits need to be filled. If the source expression operand is signed, then the upcast will extend the sign bit. If the source expression operand is unsigned, then the upcast will do a zero extend.

Examples

Expression Result Description
1i8 as i32 1 Upcasting from signed to signed will result in the same value.
-1i8 as i32 -1 The negative sign is extended resulting in the same value.
-2i8 as u64 18446744073709551614 When upcasting a signed integer, the sign is extended.
65535u16 as i64 65535 Unsigned integers are upcast by extending zeroes.

Integer Downcasting

When an integer is cast to a type with a smaller bit width, then the extra bits in the source are truncated.

Examples

Expression Result Description
-2i16 as i8 -2 These values are small enough that truncation has no effect.
-5i16 as u8 254 u8 is formed by truncating all but the least significant byte from -5.
-500i16 as i8 12 Truncation converts the negative i16 to a positive i8.

Floating Point to Integer Casting

FP to Int conversion takes the FP value and converts it to an integer value. If the FP value is outside the possible bounds of the target Integer type, then the result is a poison value. Conversion is always done by rounding towards zero.

For more details on poison values, see the LLVM Documentation.

Syntax Rules

EXPR as TYPE

Semantic Rules

S, T: Primitive Type, e: S :- e as T -> T