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